# -*- coding: utf-8 -*-
"""
ADB控制模块 - 支持USB直连和WiFi连接
支持OCR文字识别、图片识别
支持中央服务器集成（任务下发、状态上报）
"""

import subprocess
import logging
import time
import re
import os
import io
import json
import threading
from typing import Optional, List, Tuple, Union, Callable

try:
    import requests
    HAS_REQUESTS = True
except ImportError:
    HAS_REQUESTS = False

logger = logging.getLogger("fengchao.adb")


class ADBDevice:
    """
    ADB设备控制类
    
    支持两种连接方式：
    1. USB直连：电脑通过USB线连接手机
    2. WiFi连接：通过 adb connect IP:PORT 连接
    
    Example:
        >>> # USB直连（自动检测设备）
        >>> device = ADBDevice()
        >>> device.click(500, 800)
        
        >>> # 指定设备序列号
        >>> device = ADBDevice(serial="A9GVVB2B30026001")
        
        >>> # WiFi连接
        >>> device = ADBDevice.connect_wifi("192.168.1.100", 5555)
        >>> device.click(500, 800)
    """
    
    def __init__(self, serial: str = None, adb_path: str = "adb"):
        """
        初始化ADB设备
        
        Args:
            serial: 设备序列号（可选，不指定则使用第一个设备）
            adb_path: adb命令路径
        """
        self.serial = serial
        self.adb_path = adb_path
        self._screen_size = None
        self._connected = False
        
        # OCR引擎缓存（预加载后复用，提升速度）
        self._ocr_engine = None
        self._ocr_engine_type = None
        
        # 服务器集成（可选）
        self._server_url = None
        self._api_key = None
        self._device_id = None
        self._server_connected = False
        self._task_callback = None
        self._heartbeat_thread = None
        self._heartbeat_running = False
        
        # 检查连接
        self._check_connection()
    
    @classmethod
    def connect_wifi(cls, ip: str, port: int = 5555, adb_path: str = "adb") -> 'ADBDevice':
        """
        通过WiFi连接设备
        
        Args:
            ip: 设备IP地址
            port: ADB端口（默认5555）
            adb_path: adb命令路径
        
        Returns:
            ADBDevice实例
        
        Example:
            >>> # 先在手机上执行 adb tcpip 5555
            >>> device = ADBDevice.connect_wifi("192.168.1.100")
        """
        serial = f"{ip}:{port}"
        
        # 尝试连接
        result = subprocess.run(
            f"{adb_path} connect {serial}",
            shell=True, capture_output=True, text=True
        )
        
        if "connected" in result.stdout.lower() or "already" in result.stdout.lower():
            logger.info(f"✅ WiFi连接成功: {serial}")
            return cls(serial=serial, adb_path=adb_path)
        else:
            raise ConnectionError(f"WiFi连接失败: {result.stdout} {result.stderr}")
    
    @classmethod
    def enable_tcpip(cls, port: int = 5555, serial: str = None, adb_path: str = "adb") -> str:
        """
        开启设备的ADB TCP模式（需要USB连接）
        
        Args:
            port: TCP端口
            serial: 设备序列号
            adb_path: adb命令路径
        
        Returns:
            设备IP地址
        
        Example:
            >>> # USB连接状态下开启TCP模式
            >>> ip = ADBDevice.enable_tcpip(5555)
            >>> print(f"设备IP: {ip}")
            >>> # 之后可以拔掉USB线，用WiFi连接
            >>> device = ADBDevice.connect_wifi(ip)
        """
        prefix = f"{adb_path} -s {serial}" if serial else adb_path
        
        # 开启TCP模式
        result = subprocess.run(
            f"{prefix} tcpip {port}",
            shell=True, capture_output=True, text=True
        )
        
        if "restarting" not in result.stdout.lower() and result.returncode != 0:
            raise RuntimeError(f"开启TCP模式失败: {result.stderr}")
        
        logger.info(f"✅ TCP模式已开启，端口: {port}")
        
        # 获取设备IP
        ip_result = subprocess.run(
            f"{prefix} shell ip route",
            shell=True, capture_output=True, text=True,
            encoding='utf-8', errors='ignore'
        )
        
        # 解析IP
        match = re.search(r'src (\d+\.\d+\.\d+\.\d+)', ip_result.stdout)
        if match:
            ip = match.group(1)
            logger.info(f"   设备IP: {ip}")
            return ip
        
        return None
    
    @classmethod
    def list_devices(cls, adb_path: str = "adb") -> List[dict]:
        """
        列出所有连接的设备
        
        Returns:
            设备列表
        """
        result = subprocess.run(
            f"{adb_path} devices -l",
            shell=True, capture_output=True, text=True
        )
        
        devices = []
        for line in result.stdout.strip().split('\n')[1:]:
            if line.strip() and 'device' in line:
                parts = line.split()
                serial = parts[0]
                status = parts[1] if len(parts) > 1 else "unknown"
                
                # 解析额外信息
                info = {}
                for part in parts[2:]:
                    if ':' in part:
                        k, v = part.split(':', 1)
                        info[k] = v
                
                devices.append({
                    "serial": serial,
                    "status": status,
                    "model": info.get("model", ""),
                    "device": info.get("device", ""),
                    "product": info.get("product", "")
                })
        
        return devices
    
    def _check_connection(self):
        """检查设备连接状态"""
        devices = self.list_devices(self.adb_path)
        
        if not devices:
            raise ConnectionError("没有检测到ADB设备，请检查USB连接或WiFi连接")
        
        # 如果没指定serial，使用第一个设备
        if not self.serial:
            self.serial = devices[0]["serial"]
            logger.info(f"自动选择设备: {self.serial}")
        
        # 检查指定设备是否存在
        serials = [d["serial"] for d in devices]
        if self.serial not in serials:
            raise ConnectionError(f"设备 {self.serial} 未连接。可用设备: {serials}")
        
        self._connected = True
        logger.info(f"✅ 设备已连接: {self.serial}")
    
    def _adb(self, cmd: str) -> str:
        """执行ADB命令"""
        if self.serial:
            full_cmd = f"{self.adb_path} -s {self.serial} {cmd}"
        else:
            full_cmd = f"{self.adb_path} {cmd}"
        
        try:
            result = subprocess.run(
                full_cmd, shell=True, capture_output=True, 
                text=True, encoding='utf-8', errors='ignore'
            )
            
            if result.returncode != 0 and result.stderr:
                logger.warning(f"ADB命令警告: {result.stderr.strip()}")
            
            return result.stdout.strip() if result.stdout else ""
        except Exception as e:
            logger.error(f"ADB命令执行失败: {e}")
            return ""
    
    def shell(self, cmd: str) -> str:
        """执行Shell命令"""
        return self._adb(f'shell "{cmd}"')
    
    # ==================== 触摸操作 ====================
    
    def click(self, x: int, y: int) -> bool:
        """
        点击屏幕
        
        Args:
            x: X坐标
            y: Y坐标
        """
        # 确保坐标是整数
        x, y = int(x), int(y)
        self._adb(f"shell input tap {x} {y}")
        logger.debug(f"点击: ({x}, {y})")
        return True
    
    def double_click(self, x: int, y: int, interval: float = 0.1) -> bool:
        """双击"""
        self.click(x, y)
        time.sleep(interval)
        self.click(x, y)
        return True
    
    def long_press(self, x: int, y: int, duration: int = 1000) -> bool:
        """
        长按
        
        Args:
            x: X坐标
            y: Y坐标
            duration: 持续时间（毫秒）
        """
        self._adb(f"shell input swipe {x} {y} {x} {y} {duration}")
        logger.debug(f"长按: ({x}, {y}), {duration}ms")
        return True
    
    def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: int = 300) -> bool:
        """
        滑动
        
        Args:
            x1, y1: 起点坐标
            x2, y2: 终点坐标
            duration: 持续时间（毫秒）
        """
        self._adb(f"shell input swipe {x1} {y1} {x2} {y2} {duration}")
        logger.debug(f"滑动: ({x1},{y1}) -> ({x2},{y2}), {duration}ms")
        return True
    
    def swipe_up(self, duration: int = 300) -> bool:
        """上滑"""
        w, h = self.get_screen_size()
        return self.swipe(w//2, int(h*0.7), w//2, int(h*0.3), duration)
    
    def swipe_down(self, duration: int = 300) -> bool:
        """下滑"""
        w, h = self.get_screen_size()
        return self.swipe(w//2, int(h*0.3), w//2, int(h*0.7), duration)
    
    def swipe_left(self, duration: int = 300) -> bool:
        """左滑"""
        w, h = self.get_screen_size()
        return self.swipe(int(w*0.8), h//2, int(w*0.2), h//2, duration)
    
    def swipe_right(self, duration: int = 300) -> bool:
        """右滑"""
        w, h = self.get_screen_size()
        return self.swipe(int(w*0.2), h//2, int(w*0.8), h//2, duration)
    
    # ==================== 按键操作 ====================
    
    def press_key(self, keycode: str) -> bool:
        """
        按键
        
        Args:
            keycode: 按键代码，如 "back", "home", "menu", "power"
                    或数字代码如 "4" (返回)
        """
        key_map = {
            "back": "KEYCODE_BACK",
            "home": "KEYCODE_HOME",
            "menu": "KEYCODE_MENU",
            "power": "KEYCODE_POWER",
            "volume_up": "KEYCODE_VOLUME_UP",
            "volume_down": "KEYCODE_VOLUME_DOWN",
            "enter": "KEYCODE_ENTER",
            "delete": "KEYCODE_DEL",
            "tab": "KEYCODE_TAB",
            "space": "KEYCODE_SPACE",
            "recent": "KEYCODE_APP_SWITCH",
        }
        
        if keycode.lower() in key_map:
            keycode = key_map[keycode.lower()]
        elif keycode.isdigit():
            keycode = keycode
        elif not keycode.startswith("KEYCODE_"):
            keycode = f"KEYCODE_{keycode.upper()}"
        
        self._adb(f"shell input keyevent {keycode}")
        logger.debug(f"按键: {keycode}")
        return True
    
    def press_back(self) -> bool:
        """返回"""
        return self.press_key("back")
    
    def press_home(self) -> bool:
        """Home键"""
        return self.press_key("home")
    
    def press_menu(self) -> bool:
        """菜单键"""
        return self.press_key("menu")
    
    def press_recent(self) -> bool:
        """最近任务"""
        return self.press_key("recent")
    
    # ==================== 输入操作 ====================
    
    def input_text(self, text: str) -> bool:
        """
        输入文字
        
        Args:
            text: 要输入的文字
        """
        # 转义特殊字符
        escaped = text.replace(" ", "%s").replace("'", "\\'").replace('"', '\\"')
        self._adb(f'shell input text "{escaped}"')
        logger.debug(f"输入: {text}")
        return True
    
    def clear_text(self, count: int = 100) -> bool:
        """清空输入框"""
        self._adb(f"shell input keyevent --longpress $(printf 'KEYCODE_DEL %.0s' {{1..{count}}})")
        return True
    
    # ==================== 屏幕操作 ====================
    
    def get_screen_size(self) -> Tuple[int, int]:
        """获取屏幕尺寸"""
        if self._screen_size:
            return self._screen_size
        
        result = self._adb("shell wm size")
        match = re.search(r'(\d+)x(\d+)', result)
        if match:
            self._screen_size = (int(match.group(1)), int(match.group(2)))
        else:
            self._screen_size = (1080, 1920)
        
        return self._screen_size
    
    def wake_up(self) -> bool:
        """唤醒屏幕"""
        self._adb("shell input keyevent KEYCODE_WAKEUP")
        return True
    
    def lock_screen(self) -> bool:
        """锁屏"""
        self._adb("shell input keyevent KEYCODE_SLEEP")
        return True
    
    def screenshot(self, save_path: str = None) -> bytes:
        """
        截图
        
        Args:
            save_path: 保存路径（可选）
        
        Returns:
            PNG图片数据
        """
        # 截图到手机
        self._adb("shell screencap -p /sdcard/screenshot.png")
        
        # 拉取到本地
        if save_path:
            self._adb(f"pull /sdcard/screenshot.png {save_path}")
            with open(save_path, "rb") as f:
                return f.read()
        else:
            import tempfile
            with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
                temp_path = f.name
            self._adb(f"pull /sdcard/screenshot.png {temp_path}")
            with open(temp_path, "rb") as f:
                data = f.read()
            import os
            os.unlink(temp_path)
            return data
    
    # ==================== 应用操作 ====================
    
    def start_app(self, package: str, activity: str = None) -> bool:
        """
        启动应用
        
        Args:
            package: 包名
            activity: Activity名（可选）
        """
        if activity:
            self._adb(f"shell am start -n {package}/{activity}")
        else:
            self._adb(f"shell monkey -p {package} -c android.intent.category.LAUNCHER 1")
        logger.debug(f"启动应用: {package}")
        return True
    
    def stop_app(self, package: str) -> bool:
        """停止应用"""
        self._adb(f"shell am force-stop {package}")
        logger.debug(f"停止应用: {package}")
        return True
    
    def get_current_app(self) -> dict:
        """获取当前应用"""
        # 方法1: dumpsys window windows
        result = self._adb("shell dumpsys window windows")
        for line in result.split('\n'):
            if 'mCurrentFocus' in line or 'mFocusedApp' in line:
                # 匹配 com.xxx.xxx/com.xxx.Activity 格式
                match = re.search(r'([a-zA-Z][a-zA-Z0-9_.]*)/([a-zA-Z][a-zA-Z0-9_.]*)', line)
                if match:
                    return {"package": match.group(1), "activity": match.group(2)}
        
        # 方法2: dumpsys activity activities (不用grep，直接Python过滤)
        result = self._adb("shell dumpsys activity activities")
        for line in result.split('\n'):
            if 'mResumedActivity' in line or 'ResumedActivity' in line:
                match = re.search(r'([a-zA-Z][a-zA-Z0-9_.]*)/([a-zA-Z][a-zA-Z0-9_.]*)', line)
                if match:
                    return {"package": match.group(1), "activity": match.group(2)}
        
        return {"package": "", "activity": ""}
    
    def install_app(self, apk_path: str) -> bool:
        """安装应用"""
        self._adb(f"install -r {apk_path}")
        return True
    
    def uninstall_app(self, package: str) -> bool:
        """卸载应用"""
        self._adb(f"uninstall {package}")
        return True
    
    def is_app_installed(self, package: str) -> bool:
        """检查应用是否安装"""
        result = self._adb("shell pm list packages")
        return f"package:{package}" in result
    
    # ==================== 设备信息 ====================
    
    def get_device_info(self) -> dict:
        """获取设备信息"""
        return {
            "serial": self.serial,
            "model": self._adb("shell getprop ro.product.model"),
            "brand": self._adb("shell getprop ro.product.brand"),
            "android_version": self._adb("shell getprop ro.build.version.release"),
            "sdk_version": self._adb("shell getprop ro.build.version.sdk"),
            "screen_size": self.get_screen_size()
        }
    
    # ==================== 文件操作 ====================
    
    def push(self, local_path: str, remote_path: str) -> bool:
        """推送文件到设备"""
        self._adb(f'push "{local_path}" "{remote_path}"')
        logger.debug(f"推送文件: {local_path} -> {remote_path}")
        return True
    
    def pull(self, remote_path: str, local_path: str) -> bool:
        """从设备拉取文件"""
        self._adb(f'pull "{remote_path}" "{local_path}"')
        logger.debug(f"拉取文件: {remote_path} -> {local_path}")
        return True
    
    def list_files(self, path: str = "/sdcard/") -> List[str]:
        """列出目录文件"""
        result = self._adb(f'shell ls "{path}"')
        return [f for f in result.split('\n') if f.strip()]
    
    def delete_file(self, path: str) -> bool:
        """删除文件"""
        self._adb(f'shell rm -f "{path}"')
        return True
    
    def file_exists(self, path: str) -> bool:
        """检查文件是否存在"""
        result = self._adb(f'shell "[ -e \\"{path}\\" ] && echo EXISTS"')
        return "EXISTS" in result
    
    def mkdir(self, path: str) -> bool:
        """创建目录"""
        self._adb(f'shell mkdir -p "{path}"')
        return True
    
    # ==================== 录屏功能 ====================
    
    def is_screenrecord_supported(self) -> bool:
        """检查设备是否支持录屏"""
        result = self._adb("shell ls /system/bin/screenrecord 2>/dev/null")
        return bool(result and "No such file" not in result)
    
    def screenrecord(self, local_path: str, duration: int = 10, 
                     size: str = None, bit_rate: int = None) -> bool:
        """
        录屏并保存到本地（同步方式，会等待录制完成）
        
        Args:
            local_path: 本地保存路径
            duration: 录制时长（秒），最大180
            size: 分辨率，如 "720x1280"
            bit_rate: 码率，如 6000000
        
        Returns:
            是否成功
        
        Example:
            >>> device.screenrecord("video.mp4", duration=10)
        """
        # 检查是否支持
        if not self.is_screenrecord_supported():
            logger.error("该设备不支持screenrecord命令")
            return False
        
        remote_path = "/sdcard/adb_record_temp.mp4"
        
        # 构建命令
        cmd = f"shell screenrecord --time-limit {min(duration, 180)}"
        if size:
            cmd += f" --size {size}"
        if bit_rate:
            cmd += f" --bit-rate {bit_rate}"
        cmd += f' "{remote_path}"'
        
        # 同步执行录屏
        logger.info(f"开始录屏，时长: {duration}秒...")
        self._adb(cmd)
        
        # 检查文件
        import time
        time.sleep(1)
        
        if not self.file_exists(remote_path):
            logger.error("录屏失败，文件不存在")
            return False
        
        # 拉取到本地
        self.pull(remote_path, local_path)
        self.delete_file(remote_path)
        
        # 验证
        import os
        if os.path.exists(local_path) and os.path.getsize(local_path) > 0:
            logger.info(f"录屏成功: {local_path}")
            return True
        
        return False
    
    def start_screenrecord_async(self, output_path: str = "/sdcard/record.mp4", 
                                 time_limit: int = 180, size: str = None, 
                                 bit_rate: int = None) -> bool:
        """
        开始异步录屏（后台执行，需手动停止或等待超时）
        
        Args:
            output_path: 手机上的输出路径
            time_limit: 最长录制时间（秒），最大180
            size: 分辨率，如 "720x1280"
            bit_rate: 码率，如 6000000
        """
        if not self.is_screenrecord_supported():
            logger.error("该设备不支持screenrecord命令")
            return False
        
        # 使用nohup后台执行
        cmd = f"shell nohup screenrecord --time-limit {time_limit}"
        if size:
            cmd += f" --size {size}"
        if bit_rate:
            cmd += f" --bit-rate {bit_rate}"
        cmd += f' "{output_path}" > /dev/null 2>&1 &'
        
        self._adb(cmd)
        logger.info(f"开始异步录屏: {output_path}")
        return True
    
    def stop_screenrecord(self) -> bool:
        """停止录屏"""
        self._adb("shell pkill -INT screenrecord")
        return True
    
    def pull_screenrecord(self, remote_path: str = "/sdcard/record.mp4", 
                          local_path: str = "record.mp4") -> bool:
        """拉取录屏文件"""
        return self.pull(remote_path, local_path)
    
    # ==================== 应用管理扩展 ====================
    
    def list_packages(self, third_party_only: bool = False) -> List[str]:
        """列出已安装的应用包名"""
        if third_party_only:
            result = self._adb("shell pm list packages -3")
        else:
            result = self._adb("shell pm list packages")
        
        packages = []
        for line in result.split('\n'):
            if line.startswith("package:"):
                packages.append(line.replace("package:", "").strip())
        return packages
    
    def clear_app_data(self, package: str) -> bool:
        """清除应用数据"""
        self._adb(f"shell pm clear {package}")
        logger.debug(f"清除应用数据: {package}")
        return True
    
    def grant_permission(self, package: str, permission: str) -> bool:
        """授予应用权限"""
        self._adb(f"shell pm grant {package} {permission}")
        return True
    
    def revoke_permission(self, package: str, permission: str) -> bool:
        """撤销应用权限"""
        self._adb(f"shell pm revoke {package} {permission}")
        return True
    
    def disable_app(self, package: str) -> bool:
        """禁用应用"""
        self._adb(f"shell pm disable-user --user 0 {package}")
        return True
    
    def enable_app(self, package: str) -> bool:
        """启用应用"""
        self._adb(f"shell pm enable {package}")
        return True
    
    def get_apk_path(self, package: str) -> str:
        """获取应用APK路径"""
        result = self._adb(f"shell pm path {package}")
        if result.startswith("package:"):
            return result.replace("package:", "").strip()
        return ""
    
    # ==================== 系统设置 ====================
    
    def get_brightness(self) -> int:
        """获取屏幕亮度 (0-255)"""
        result = self._adb("shell settings get system screen_brightness")
        try:
            return int(result.strip())
        except:
            return 0
    
    def set_brightness(self, level: int) -> bool:
        """设置屏幕亮度 (0-255)"""
        level = max(0, min(255, level))
        self._adb(f"shell settings put system screen_brightness {level}")
        return True
    
    def set_auto_brightness(self, enabled: bool) -> bool:
        """设置自动亮度"""
        value = 1 if enabled else 0
        self._adb(f"shell settings put system screen_brightness_mode {value}")
        return True
    
    def enable_wifi(self) -> bool:
        """开启WiFi"""
        self._adb("shell svc wifi enable")
        return True
    
    def disable_wifi(self) -> bool:
        """关闭WiFi"""
        self._adb("shell svc wifi disable")
        return True
    
    def enable_mobile_data(self) -> bool:
        """开启移动数据"""
        self._adb("shell svc data enable")
        return True
    
    def disable_mobile_data(self) -> bool:
        """关闭移动数据"""
        self._adb("shell svc data disable")
        return True
    
    def enable_airplane_mode(self) -> bool:
        """开启飞行模式"""
        self._adb("shell settings put global airplane_mode_on 1")
        self._adb("shell am broadcast -a android.intent.action.AIRPLANE_MODE")
        return True
    
    def disable_airplane_mode(self) -> bool:
        """关闭飞行模式"""
        self._adb("shell settings put global airplane_mode_on 0")
        self._adb("shell am broadcast -a android.intent.action.AIRPLANE_MODE")
        return True
    
    def set_screen_timeout(self, timeout_ms: int) -> bool:
        """设置屏幕超时时间（毫秒）"""
        self._adb(f"shell settings put system screen_off_timeout {timeout_ms}")
        return True
    
    def get_density(self) -> int:
        """获取屏幕密度"""
        result = self._adb("shell wm density")
        match = re.search(r'(\d+)', result)
        return int(match.group(1)) if match else 0
    
    def set_density(self, density: int) -> bool:
        """设置屏幕密度"""
        self._adb(f"shell wm density {density}")
        return True
    
    def reset_density(self) -> bool:
        """重置屏幕密度"""
        self._adb("shell wm density reset")
        return True
    
    def set_screen_resolution(self, width: int, height: int) -> bool:
        """设置屏幕分辨率"""
        self._adb(f"shell wm size {width}x{height}")
        self._screen_size = (width, height)
        return True
    
    def reset_screen_resolution(self) -> bool:
        """重置屏幕分辨率"""
        self._adb("shell wm size reset")
        self._screen_size = None
        return True
    
    # ==================== 电池与设备状态 ====================
    
    def get_battery_level(self) -> int:
        """获取电池电量"""
        result = self._adb("shell dumpsys battery")
        for line in result.split('\n'):
            if 'level' in line.lower():
                match = re.search(r'level:\s*(\d+)', line)
                if match:
                    return int(match.group(1))
        return 0
    
    def get_battery_status(self) -> dict:
        """获取电池详细状态"""
        result = self._adb("shell dumpsys battery")
        status = {}
        for line in result.split('\n'):
            if ':' in line:
                parts = line.split(':', 1)
                if len(parts) == 2:
                    key = parts[0].strip().lower().replace(' ', '_')
                    value = parts[1].strip()
                    status[key] = value
        return status
    
    def get_ip_address(self) -> str:
        """获取设备IP地址"""
        result = self._adb("shell ip route")
        for line in result.split('\n'):
            if 'wlan' in line:
                match = re.search(r'src (\d+\.\d+\.\d+\.\d+)', line)
                if match:
                    return match.group(1)
        
        # 备用方案: ip addr
        result = self._adb("shell ip addr show wlan0")
        match = re.search(r'inet (\d+\.\d+\.\d+\.\d+)', result)
        if match:
            return match.group(1)
        
        return ""
    
    def get_mac_address(self) -> str:
        """获取MAC地址"""
        result = self._adb("shell cat /sys/class/net/wlan0/address")
        return result.strip() if result else ""
    
    def is_screen_on(self) -> bool:
        """检查屏幕是否亮着"""
        result = self._adb("shell dumpsys power")
        # 查找 mScreenOn 或 Display Power: state
        if 'mScreenOn=true' in result:
            return True
        if 'Display Power: state=ON' in result:
            return True
        if 'mWakefulness=Awake' in result:
            return True
        return False
    
    def is_screen_locked(self) -> bool:
        """检查屏幕是否锁定"""
        result = self._adb("shell dumpsys window")
        for line in result.split('\n'):
            if 'mDreamingLockscreen' in line:
                return 'true' in line.lower()
            if 'isStatusBarKeyguard' in line:
                return 'true' in line.lower()
        return False
    
    # ==================== 剪贴板操作 ====================
    
    def set_clipboard(self, text: str) -> bool:
        """设置剪贴板内容"""
        # 方法1：使用am broadcast（需要Clipper应用）
        escaped = text.replace('"', '\\"').replace("'", "\\'")
        self._adb(f'shell am broadcast -a clipper.set -e text "{escaped}"')
        return True
    
    def input_from_clipboard(self) -> bool:
        """从剪贴板粘贴（模拟Ctrl+V）"""
        self._adb("shell input keyevent 279")
        return True
    
    # ==================== URL和Intent ====================
    
    def open_url(self, url: str) -> bool:
        """打开URL"""
        self._adb(f'shell am start -a android.intent.action.VIEW -d "{url}"')
        return True
    
    def call_phone(self, number: str) -> bool:
        """拨打电话"""
        self._adb(f'shell am start -a android.intent.action.CALL -d "tel:{number}"')
        return True
    
    def send_sms(self, number: str, message: str = "") -> bool:
        """打开短信界面"""
        self._adb(f'shell am start -a android.intent.action.SENDTO -d "sms:{number}" --es sms_body "{message}"')
        return True
    
    def open_settings(self, setting: str = "") -> bool:
        """
        打开设置页面
        
        Args:
            setting: 设置页面名称，如 "wifi", "bluetooth", "display"
        """
        settings_map = {
            "": "android.settings.SETTINGS",
            "wifi": "android.settings.WIFI_SETTINGS",
            "bluetooth": "android.settings.BLUETOOTH_SETTINGS",
            "display": "android.settings.DISPLAY_SETTINGS",
            "sound": "android.settings.SOUND_SETTINGS",
            "apps": "android.settings.APPLICATION_SETTINGS",
            "location": "android.settings.LOCATION_SOURCE_SETTINGS",
            "security": "android.settings.SECURITY_SETTINGS",
            "developer": "android.settings.APPLICATION_DEVELOPMENT_SETTINGS",
            "date": "android.settings.DATE_SETTINGS",
            "accessibility": "android.settings.ACCESSIBILITY_SETTINGS",
        }
        action = settings_map.get(setting.lower(), settings_map[""])
        self._adb(f'shell am start -a {action}')
        return True
    
    # ==================== 日志功能 ====================
    
    def get_logcat(self, tag: str = None, level: str = None, 
                   lines: int = 100) -> str:
        """
        获取日志
        
        Args:
            tag: 过滤标签
            level: 日志级别 (V/D/I/W/E)
            lines: 返回行数
        """
        cmd = "shell logcat -d"
        if tag:
            cmd += f" -s {tag}"
        if level:
            cmd += f" *:{level}"
        
        result = self._adb(cmd)
        lines_list = result.split('\n')
        return '\n'.join(lines_list[-lines:])
    
    def clear_logcat(self) -> bool:
        """清除日志"""
        self._adb("shell logcat -c")
        return True
    
    # ==================== 设备控制 ====================
    
    def reboot(self, mode: str = None) -> bool:
        """
        重启设备
        
        Args:
            mode: 重启模式 - None(正常), "recovery", "bootloader"
        """
        if mode:
            self._adb(f"reboot {mode}")
        else:
            self._adb("reboot")
        return True
    
    def shutdown(self) -> bool:
        """关机"""
        self._adb("shell reboot -p")
        return True
    
    def disconnect(self) -> bool:
        """断开WiFi连接"""
        if ":" in self.serial:  # WiFi连接
            self._adb(f"disconnect {self.serial}")
            logger.info(f"已断开: {self.serial}")
        return True
    
    @classmethod
    def disconnect_all(cls, adb_path: str = "adb") -> bool:
        """断开所有WiFi连接"""
        subprocess.run(f"{adb_path} disconnect", shell=True)
        return True
    
    @classmethod
    def kill_server(cls, adb_path: str = "adb") -> bool:
        """杀死ADB服务"""
        subprocess.run(f"{adb_path} kill-server", shell=True)
        return True
    
    @classmethod
    def start_server(cls, adb_path: str = "adb") -> bool:
        """启动ADB服务"""
        subprocess.run(f"{adb_path} start-server", shell=True)
        return True
    
    # ==================== 端口转发 ====================
    
    def forward(self, local_port: int, remote_port: int) -> bool:
        """端口转发"""
        self._adb(f"forward tcp:{local_port} tcp:{remote_port}")
        return True
    
    def forward_remove(self, local_port: int) -> bool:
        """移除端口转发"""
        self._adb(f"forward --remove tcp:{local_port}")
        return True
    
    def reverse(self, remote_port: int, local_port: int) -> bool:
        """反向端口转发"""
        self._adb(f"reverse tcp:{remote_port} tcp:{local_port}")
        return True
    
    def reverse_remove(self, remote_port: int) -> bool:
        """移除反向端口转发"""
        self._adb(f"reverse --remove tcp:{remote_port}")
        return True
    
    # ==================== 输入法相关 ====================
    
    def get_current_ime(self) -> str:
        """获取当前输入法"""
        result = self._adb("shell settings get secure default_input_method")
        return result.strip()
    
    def list_ime(self) -> List[str]:
        """列出所有输入法"""
        result = self._adb("shell ime list -s")
        return [ime.strip() for ime in result.split('\n') if ime.strip()]
    
    def set_ime(self, ime_id: str) -> bool:
        """设置输入法"""
        self._adb(f"shell ime set {ime_id}")
        return True
    
    # ==================== 性能监控 ====================
    
    def get_cpu_usage(self, package: str = None) -> str:
        """获取CPU使用情况"""
        result = self._adb("shell top -n 1")
        lines = result.split('\n')
        if package:
            for line in lines:
                if package in line:
                    return line
            return ""
        return '\n'.join(lines[:20])
    
    def get_memory_info(self, package: str = None) -> str:
        """获取内存信息"""
        if package:
            return self._adb(f"shell dumpsys meminfo {package}")
        result = self._adb("shell cat /proc/meminfo")
        lines = result.split('\n')
        return '\n'.join(lines[:10])
    
    def get_fps(self, package: str) -> str:
        """获取应用帧率信息"""
        result = self._adb(f"shell dumpsys gfxinfo {package}")
        for line in result.split('\n'):
            if 'Total frames' in line:
                return line.strip()
        return ""
    
    # ==================== OCR文字识别 ====================
    
    def _get_screenshot_bytes(self) -> bytes:
        """获取截图字节数据（直接通过exec-out获取）"""
        if self.serial:
            cmd = f"{self.adb_path} -s {self.serial} exec-out screencap -p"
        else:
            cmd = f"{self.adb_path} exec-out screencap -p"
        
        result = subprocess.run(cmd, shell=True, capture_output=True)
        
        if result.returncode == 0 and result.stdout:
            return result.stdout
        
        # 备用方案：通过临时文件
        import tempfile
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            temp_path = f.name
        
        self._adb(f"shell screencap -p /sdcard/ocr_temp.png")
        self._adb(f"pull /sdcard/ocr_temp.png {temp_path}")
        self._adb("shell rm /sdcard/ocr_temp.png")
        
        with open(temp_path, "rb") as f:
            data = f.read()
        os.unlink(temp_path)
        return data
    
    def _get_screenshot_image(self):
        """获取截图PIL Image对象"""
        try:
            from PIL import Image
        except ImportError:
            raise ImportError("需要安装Pillow: pip install Pillow")
        
        data = self._get_screenshot_bytes()
        return Image.open(io.BytesIO(data))
    
    def init_ocr(self, engine: str = "rapidocr"):
        """
        预加载OCR引擎（提升后续识别速度）
        
        Args:
            engine: OCR引擎 - "rapidocr"(推荐,快速), "easyocr", "paddleocr"
        
        Example:
            >>> device.init_ocr()  # 预加载，约2秒
            >>> device.click_text("登录")  # 后续识别只需3-4秒
        """
        if self._ocr_engine_type == engine and self._ocr_engine:
            return  # 已加载
        
        if engine == "rapidocr":
            try:
                from rapidocr_onnxruntime import RapidOCR
                self._ocr_engine = RapidOCR()
                self._ocr_engine_type = "rapidocr"
                logger.info("RapidOCR引擎已加载")
            except ImportError:
                raise ImportError("需要安装RapidOCR: pip install rapidocr-onnxruntime")
        elif engine == "easyocr":
            try:
                import easyocr
                self._ocr_engine = easyocr.Reader(['ch_sim', 'en'], gpu=False)
                self._ocr_engine_type = "easyocr"
                logger.info("EasyOCR引擎已加载")
            except ImportError:
                raise ImportError("需要安装EasyOCR: pip install easyocr")
        else:
            raise ValueError(f"不支持预加载的引擎: {engine}")
    
    def ocr_screen(self, engine: str = "rapidocr", lang: str = "ch_sim", 
                   scale: float = 1.0) -> List[dict]:
        """
        OCR识别屏幕上的文字
        
        Args:
            engine: OCR引擎 - "rapidocr"(推荐,快速), "easyocr", "paddleocr", "tesseract"
            lang: 语言 - "ch_sim"(简体中文), "en"(英文)
            scale: 图片缩放比例(0.5可加速但降低精度)
        
        Returns:
            识别结果列表，每项包含:
            - text: 识别的文字
            - box: 边界框 [x1, y1, x2, y2]
            - center: 中心点 (x, y)
            - confidence: 置信度
        
        Example:
            >>> device.init_ocr()  # 可选：预加载提速
            >>> results = device.ocr_screen()
            >>> for r in results:
            ...     print(f"{r['text']} at {r['center']}")
        """
        img = self._get_screenshot_image()
        
        # 可选缩放（加速识别）
        if scale != 1.0:
            new_size = (int(img.width * scale), int(img.height * scale))
            img = img.resize(new_size)
        
        results = []
        
        if engine == "rapidocr":
            results = self._ocr_rapidocr(img, scale)
        elif engine == "easyocr":
            results = self._ocr_easyocr(img, lang, scale)
        elif engine == "paddleocr":
            results = self._ocr_paddleocr(img, lang, scale)
        elif engine == "tesseract":
            results = self._ocr_tesseract(img, lang, scale)
        else:
            raise ValueError(f"不支持的OCR引擎: {engine}")
        
        return results
    
    def _ocr_rapidocr(self, img, scale: float = 1.0) -> List[dict]:
        """使用RapidOCR识别（快速推荐）"""
        try:
            from rapidocr_onnxruntime import RapidOCR
            import numpy as np
        except ImportError:
            raise ImportError("需要安装RapidOCR: pip install rapidocr-onnxruntime")
        
        # 使用缓存的引擎或创建新的
        if self._ocr_engine_type == "rapidocr" and self._ocr_engine:
            ocr = self._ocr_engine
        else:
            ocr = RapidOCR()
            self._ocr_engine = ocr
            self._ocr_engine_type = "rapidocr"
        
        img_array = np.array(img)
        ocr_results, _ = ocr(img_array)
        
        results = []
        if ocr_results:
            for item in ocr_results:
                box, text, conf = item
                x1 = int(min(p[0] for p in box) / scale)
                y1 = int(min(p[1] for p in box) / scale)
                x2 = int(max(p[0] for p in box) / scale)
                y2 = int(max(p[1] for p in box) / scale)
                
                results.append({
                    "text": text,
                    "box": [x1, y1, x2, y2],
                    "center": ((x1 + x2) // 2, (y1 + y2) // 2),
                    "confidence": conf
                })
        
        return results
    
    def _ocr_easyocr(self, img, lang: str, scale: float = 1.0) -> List[dict]:
        """使用EasyOCR识别"""
        try:
            import easyocr
            import numpy as np
        except ImportError:
            raise ImportError("需要安装EasyOCR: pip install easyocr")
        
        # 语言映射
        lang_map = {
            "ch_sim": ["ch_sim", "en"],
            "ch_tra": ["ch_tra", "en"],
            "en": ["en"],
            "ja": ["ja", "en"],
            "ko": ["ko", "en"],
        }
        langs = lang_map.get(lang, ["ch_sim", "en"])
        
        reader = easyocr.Reader(langs, gpu=False)
        img_array = np.array(img)
        ocr_results = reader.readtext(img_array)
        
        results = []
        for (box, text, conf) in ocr_results:
            x1 = int(min(p[0] for p in box) / scale)
            y1 = int(min(p[1] for p in box) / scale)
            x2 = int(max(p[0] for p in box) / scale)
            y2 = int(max(p[1] for p in box) / scale)
            
            results.append({
                "text": text,
                "box": [x1, y1, x2, y2],
                "center": ((x1 + x2) // 2, (y1 + y2) // 2),
                "confidence": conf
            })
        
        return results
    
    def _ocr_paddleocr(self, img, lang: str, scale: float = 1.0) -> List[dict]:
        """使用PaddleOCR识别"""
        try:
            from paddleocr import PaddleOCR
            import numpy as np
        except ImportError:
            raise ImportError("需要安装PaddleOCR: pip install paddleocr paddlepaddle")
        
        ocr = PaddleOCR(use_angle_cls=True, lang=lang, show_log=False)
        img_array = np.array(img)
        ocr_results = ocr.ocr(img_array, cls=True)
        
        results = []
        if ocr_results and ocr_results[0]:
            for line in ocr_results[0]:
                box = line[0]
                text = line[1][0]
                conf = line[1][1]
                
                x1 = int(min(p[0] for p in box) / scale)
                y1 = int(min(p[1] for p in box) / scale)
                x2 = int(max(p[0] for p in box) / scale)
                y2 = int(max(p[1] for p in box) / scale)
                
                results.append({
                    "text": text,
                    "box": [x1, y1, x2, y2],
                    "center": ((x1 + x2) // 2, (y1 + y2) // 2),
                    "confidence": conf
                })
        
        return results
    
    def _ocr_tesseract(self, img, lang: str, scale: float = 1.0) -> List[dict]:
        """使用Tesseract识别"""
        try:
            import pytesseract
        except ImportError:
            raise ImportError("需要安装pytesseract: pip install pytesseract")
        
        # 语言映射
        lang_map = {
            "ch_sim": "chi_sim",
            "ch_tra": "chi_tra",
            "en": "eng",
            "ja": "jpn",
            "ko": "kor",
        }
        tess_lang = lang_map.get(lang, "chi_sim+eng")
        
        data = pytesseract.image_to_data(img, lang=tess_lang, output_type=pytesseract.Output.DICT)
        
        results = []
        n_boxes = len(data['text'])
        for i in range(n_boxes):
            text = data['text'][i].strip()
            conf = int(data['conf'][i])
            
            if text and conf > 0:
                x = data['left'][i]
                y = data['top'][i]
                w = data['width'][i]
                h = data['height'][i]
                
                x = int(x / scale)
                y = int(y / scale)
                w = int(w / scale)
                h = int(h / scale)
                results.append({
                    "text": text,
                    "box": [x, y, x + w, y + h],
                    "center": (x + w // 2, y + h // 2),
                    "confidence": conf / 100
                })
        
        return results
    
    def find_text(self, text: str, engine: str = "rapidocr", 
                  exact: bool = False) -> Optional[Tuple[int, int]]:
        """
        查找文字位置
        
        Args:
            text: 要查找的文字
            engine: OCR引擎
            exact: 是否精确匹配，False则包含匹配
        
        Returns:
            找到返回中心坐标(x, y)，否则返回None
        
        Example:
            >>> pos = device.find_text("登录")
            >>> if pos:
            ...     device.click(*pos)
        """
        results = self.ocr_screen(engine=engine)
        
        for r in results:
            if exact:
                if r['text'] == text:
                    return r['center']
            else:
                if text in r['text']:
                    return r['center']
        
        return None
    
    def find_all_text(self, text: str, engine: str = "rapidocr",
                      exact: bool = False) -> List[Tuple[int, int]]:
        """
        查找所有匹配文字的位置
        
        Returns:
            所有匹配位置的列表
        """
        results = self.ocr_screen(engine=engine)
        positions = []
        
        for r in results:
            if exact:
                if r['text'] == text:
                    positions.append(r['center'])
            else:
                if text in r['text']:
                    positions.append(r['center'])
        
        return positions
    
    def click_text(self, text: str, engine: str = "rapidocr",
                   exact: bool = False, timeout: float = 0) -> bool:
        """
        点击指定文字
        
        Args:
            text: 要点击的文字
            engine: OCR引擎
            exact: 是否精确匹配
            timeout: 超时时间（秒），0表示不等待
        
        Returns:
            是否成功点击
        
        Example:
            >>> device.click_text("登录")
            >>> device.click_text("确定", timeout=5)  # 等待5秒
        """
        start_time = time.time()
        
        while True:
            pos = self.find_text(text, engine=engine, exact=exact)
            if pos:
                self.click(*pos)
                logger.info(f"点击文字 '{text}' at {pos}")
                return True
            
            if timeout <= 0 or (time.time() - start_time) >= timeout:
                break
            
            time.sleep(0.5)
        
        logger.warning(f"未找到文字: {text}")
        return False
    
    def wait_for_text(self, text: str, timeout: float = 10,
                      engine: str = "easyocr") -> bool:
        """
        等待文字出现
        
        Args:
            text: 要等待的文字
            timeout: 超时时间（秒）
            engine: OCR引擎
        
        Returns:
            是否找到文字
        """
        start_time = time.time()
        
        while (time.time() - start_time) < timeout:
            pos = self.find_text(text, engine=engine)
            if pos:
                logger.info(f"找到文字 '{text}' at {pos}")
                return True
            time.sleep(0.5)
        
        return False
    
    def text_exists(self, text: str, engine: str = "easyocr") -> bool:
        """检查文字是否存在"""
        return self.find_text(text, engine=engine) is not None
    
    # ==================== UI元素分析（XML） ====================
    
    def dump_ui(self, save_path: str = None) -> str:
        """
        获取当前界面的UI XML结构
        
        Args:
            save_path: 保存XML文件的路径（可选）
        
        Returns:
            XML字符串
        
        Example:
            >>> xml = device.dump_ui()
            >>> print(xml)
        """
        # 在设备上dump UI
        self._adb("shell uiautomator dump /sdcard/ui.xml")
        
        # 读取XML内容
        xml_content = self._adb("shell cat /sdcard/ui.xml")
        
        # 清理临时文件
        self._adb("shell rm /sdcard/ui.xml")
        
        # 保存到本地（可选）
        if save_path:
            with open(save_path, 'w', encoding='utf-8') as f:
                f.write(xml_content)
        
        return xml_content
    
    def get_ui_elements(self) -> List[dict]:
        """
        解析UI XML，返回所有可交互元素列表
        
        Returns:
            元素列表，每个元素包含:
            - text: 文字
            - resource_id: 资源ID
            - class_name: 类名
            - content_desc: 描述
            - bounds: 边界 [x1, y1, x2, y2]
            - center: 中心点 (x, y)
            - clickable: 是否可点击
        
        Example:
            >>> elements = device.get_ui_elements()
            >>> for e in elements:
            ...     print(f"{e['text']} @ {e['center']}")
        """
        import xml.etree.ElementTree as ET
        import re
        
        xml_content = self.dump_ui()
        
        elements = []
        try:
            root = ET.fromstring(xml_content)
            
            for node in root.iter('node'):
                bounds_str = node.get('bounds', '')
                # 解析bounds: [x1,y1][x2,y2]
                match = re.findall(r'\[(\d+),(\d+)\]', bounds_str)
                if len(match) == 2:
                    x1, y1 = int(match[0][0]), int(match[0][1])
                    x2, y2 = int(match[1][0]), int(match[1][1])
                    center = ((x1 + x2) // 2, (y1 + y2) // 2)
                else:
                    x1, y1, x2, y2 = 0, 0, 0, 0
                    center = (0, 0)
                
                elements.append({
                    'text': node.get('text', ''),
                    'resource_id': node.get('resource-id', ''),
                    'class_name': node.get('class', ''),
                    'content_desc': node.get('content-desc', ''),
                    'bounds': [x1, y1, x2, y2],
                    'center': center,
                    'clickable': node.get('clickable', 'false') == 'true',
                    'package': node.get('package', ''),
                })
        except ET.ParseError as e:
            logger.error(f"解析UI XML失败: {e}")
        
        return elements
    
    def find_element_by_text(self, text: str, exact: bool = False) -> Optional[dict]:
        """
        通过文字查找UI元素
        
        Args:
            text: 要查找的文字
            exact: 是否精确匹配
        
        Returns:
            找到返回元素信息，否则返回None
        
        Example:
            >>> elem = device.find_element_by_text("点赞")
            >>> if elem:
            ...     device.click(*elem['center'])
        """
        elements = self.get_ui_elements()
        for e in elements:
            if exact:
                if e['text'] == text or e['content_desc'] == text:
                    return e
            else:
                if text in e['text'] or text in e['content_desc']:
                    return e
        return None
    
    def find_element_by_id(self, resource_id: str) -> Optional[dict]:
        """
        通过资源ID查找UI元素
        
        Args:
            resource_id: 资源ID（可以是部分匹配）
        
        Returns:
            找到返回元素信息，否则返回None
        
        Example:
            >>> elem = device.find_element_by_id("like_button")
            >>> if elem:
            ...     device.click(*elem['center'])
        """
        elements = self.get_ui_elements()
        for e in elements:
            if resource_id in e['resource_id']:
                return e
        return None
    
    def find_element_by_desc(self, desc: str, exact: bool = False) -> Optional[dict]:
        """
        通过content-desc查找UI元素
        
        Args:
            desc: 描述文字
            exact: 是否精确匹配
        
        Returns:
            找到返回元素信息，否则返回None
        """
        elements = self.get_ui_elements()
        for e in elements:
            if exact:
                if e['content_desc'] == desc:
                    return e
            else:
                if desc in e['content_desc']:
                    return e
        return None
    
    def find_elements_by_class(self, class_name: str) -> List[dict]:
        """
        通过类名查找所有匹配的UI元素
        
        Args:
            class_name: 类名（可以是部分匹配，如 "ImageView"）
        
        Returns:
            匹配的元素列表
        """
        elements = self.get_ui_elements()
        return [e for e in elements if class_name in e['class_name']]
    
    def click_element(self, element: dict) -> bool:
        """
        点击UI元素
        
        Args:
            element: 元素信息（需包含center字段）
        
        Returns:
            是否成功
        """
        if element and 'center' in element:
            self.click(*element['center'])
            return True
        return False
    
    def click_by_text(self, text: str, exact: bool = False) -> bool:
        """
        通过文字查找并点击元素
        
        Args:
            text: 要查找的文字
            exact: 是否精确匹配
        
        Returns:
            是否成功点击
        
        Example:
            >>> device.click_by_text("登录")
            >>> device.click_by_text("点赞")
        """
        elem = self.find_element_by_text(text, exact)
        if elem:
            self.click(*elem['center'])
            logger.info(f"点击元素 '{text}' @ {elem['center']}")
            return True
        logger.warning(f"未找到文字元素: {text}")
        return False
    
    def click_by_id(self, resource_id: str) -> bool:
        """
        通过资源ID查找并点击元素
        
        Args:
            resource_id: 资源ID
        
        Returns:
            是否成功点击
        
        Example:
            >>> device.click_by_id("com.smile.gifmaker:id/like")
        """
        elem = self.find_element_by_id(resource_id)
        if elem:
            self.click(*elem['center'])
            logger.info(f"点击元素 ID={resource_id} @ {elem['center']}")
            return True
        logger.warning(f"未找到ID元素: {resource_id}")
        return False
    
    def click_by_desc(self, desc: str, exact: bool = False) -> bool:
        """
        通过content-desc查找并点击元素
        
        Args:
            desc: 描述文字
            exact: 是否精确匹配
        
        Returns:
            是否成功点击
        
        Example:
            >>> device.click_by_desc("点赞")
        """
        elem = self.find_element_by_desc(desc, exact)
        if elem:
            self.click(*elem['center'])
            logger.info(f"点击元素 desc={desc} @ {elem['center']}")
            return True
        logger.warning(f"未找到描述元素: {desc}")
        return False
    
    def print_ui_tree(self, clickable_only: bool = False):
        """
        打印UI元素树（调试用）
        
        Args:
            clickable_only: 是否只显示可点击元素
        """
        elements = self.get_ui_elements()
        print(f"共 {len(elements)} 个元素:")
        print("-" * 60)
        for e in elements:
            if clickable_only and not e['clickable']:
                continue
            text = e['text'] or e['content_desc'] or e['resource_id'] or e['class_name']
            print(f"  {text[:30]:30} @ {e['center']} {'[可点击]' if e['clickable'] else ''}")
    
    # ==================== 图片识别（模板匹配） ====================
    
    def find_image(self, template_path: str, threshold: float = 0.8,
                   grayscale: bool = True) -> Optional[dict]:
        """
        在屏幕上查找图片（模板匹配）
        
        Args:
            template_path: 模板图片路径
            threshold: 匹配阈值 (0-1)，越高越严格
            grayscale: 是否转灰度匹配（更快）
        
        Returns:
            找到返回 {"center": (x,y), "box": [x1,y1,x2,y2], "confidence": 0.95}
            否则返回 None
        
        Example:
            >>> result = device.find_image("button.png")
            >>> if result:
            ...     device.click(*result['center'])
        """
        try:
            import cv2
            import numpy as np
        except ImportError:
            raise ImportError("需要安装OpenCV: pip install opencv-python")
        
        # 获取屏幕截图
        screen = self._get_screenshot_image()
        screen_array = np.array(screen)
        
        # 转换颜色空间 (PIL是RGB，OpenCV需要BGR)
        screen_cv = cv2.cvtColor(screen_array, cv2.COLOR_RGB2BGR)
        
        # 读取模板图片（支持中文路径）
        template = cv2.imdecode(np.fromfile(template_path, dtype=np.uint8), cv2.IMREAD_COLOR)
        if template is None:
            raise FileNotFoundError(f"模板图片不存在: {template_path}")
        
        # 转灰度
        if grayscale:
            screen_cv = cv2.cvtColor(screen_cv, cv2.COLOR_BGR2GRAY)
            template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
        
        # 模板匹配
        result = cv2.matchTemplate(screen_cv, template, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
        
        if max_val >= threshold:
            h, w = template.shape[:2]
            x1, y1 = max_loc
            x2, y2 = x1 + w, y1 + h
            center = (x1 + w // 2, y1 + h // 2)
            
            return {
                "center": center,
                "box": [x1, y1, x2, y2],
                "confidence": max_val
            }
        
        return None
    
    def find_all_images(self, template_path: str, threshold: float = 0.8,
                        grayscale: bool = True, max_count: int = 10) -> List[dict]:
        """
        查找屏幕上所有匹配的图片
        
        Args:
            template_path: 模板图片路径
            threshold: 匹配阈值
            grayscale: 是否转灰度
            max_count: 最多返回数量
        
        Returns:
            匹配结果列表
        """
        try:
            import cv2
            import numpy as np
        except ImportError:
            raise ImportError("需要安装OpenCV: pip install opencv-python")
        
        screen = self._get_screenshot_image()
        screen_array = np.array(screen)
        screen_cv = cv2.cvtColor(screen_array, cv2.COLOR_RGB2BGR)
        
        # 读取模板图片（支持中文路径）
        template = cv2.imdecode(np.fromfile(template_path, dtype=np.uint8), cv2.IMREAD_COLOR)
        if template is None:
            raise FileNotFoundError(f"模板图片不存在: {template_path}")
        
        if grayscale:
            screen_cv = cv2.cvtColor(screen_cv, cv2.COLOR_BGR2GRAY)
            template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
        
        h, w = template.shape[:2]
        result = cv2.matchTemplate(screen_cv, template, cv2.TM_CCOEFF_NORMED)
        
        # 找所有匹配点
        locations = np.where(result >= threshold)
        matches = []
        
        for pt in zip(*locations[::-1]):
            x1, y1 = pt
            x2, y2 = x1 + w, y1 + h
            center = (x1 + w // 2, y1 + h // 2)
            confidence = result[y1, x1]
            
            # 去重（合并相近的点）
            is_duplicate = False
            for m in matches:
                if abs(m['center'][0] - center[0]) < w // 2 and \
                   abs(m['center'][1] - center[1]) < h // 2:
                    is_duplicate = True
                    break
            
            if not is_duplicate:
                matches.append({
                    "center": center,
                    "box": [x1, y1, x2, y2],
                    "confidence": float(confidence)
                })
            
            if len(matches) >= max_count:
                break
        
        # 按置信度排序
        matches.sort(key=lambda x: x['confidence'], reverse=True)
        return matches
    
    def click_image(self, template_path: str, threshold: float = 0.8,
                    timeout: float = 0) -> bool:
        """
        点击匹配的图片
        
        Args:
            template_path: 模板图片路径
            threshold: 匹配阈值
            timeout: 超时时间（秒），0表示不等待
        
        Returns:
            是否成功点击
        
        Example:
            >>> device.click_image("login_button.png")
            >>> device.click_image("confirm.png", timeout=5)
        """
        start_time = time.time()
        
        while True:
            result = self.find_image(template_path, threshold=threshold)
            if result:
                self.click(*result['center'])
                logger.info(f"点击图片 {template_path} at {result['center']}")
                return True
            
            if timeout <= 0 or (time.time() - start_time) >= timeout:
                break
            
            time.sleep(0.5)
        
        logger.warning(f"未找到图片: {template_path}")
        return False
    
    def wait_for_image(self, template_path: str, timeout: float = 10,
                       threshold: float = 0.8) -> bool:
        """
        等待图片出现
        
        Args:
            template_path: 模板图片路径
            timeout: 超时时间（秒）
            threshold: 匹配阈值
        
        Returns:
            是否找到图片
        """
        start_time = time.time()
        
        while (time.time() - start_time) < timeout:
            result = self.find_image(template_path, threshold=threshold)
            if result:
                logger.info(f"找到图片 {template_path} at {result['center']}")
                return True
            time.sleep(0.5)
        
        return False
    
    def image_exists(self, template_path: str, threshold: float = 0.8) -> bool:
        """检查图片是否存在于屏幕上"""
        return self.find_image(template_path, threshold=threshold) is not None
    
    def save_screenshot_region(self, box: Tuple[int, int, int, int], 
                               save_path: str) -> bool:
        """
        保存屏幕区域作为模板图片
        
        Args:
            box: 区域 (x1, y1, x2, y2)
            save_path: 保存路径
        
        Example:
            >>> # 保存屏幕区域作为模板
            >>> device.save_screenshot_region((100, 200, 300, 400), "template.png")
        """
        img = self._get_screenshot_image()
        region = img.crop(box)
        region.save(save_path)
        logger.info(f"保存模板: {save_path}")
        return True
    
    def __repr__(self):
        return f"ADBDevice(serial='{self.serial}')"
    
    # ==================== 中央服务器集成 ====================
    
    def connect_server(self, server_url: str, api_key: str, 
                       device_id: str = None, auto_heartbeat: bool = True) -> bool:
        """
        连接中央服务器
        
        Args:
            server_url: 服务器地址，如 "http://your-server.com"
            api_key: API密钥
            device_id: 设备ID（可选，不指定则使用serial）
            auto_heartbeat: 是否自动发送心跳
        
        Returns:
            是否连接成功
        
        Example:
            >>> device = ADBDevice()
            >>> device.connect_server(
            ...     server_url="http://192.168.1.100:8000",
            ...     api_key="sk-xxxxx"
            ... )
        """
        if not HAS_REQUESTS:
            raise ImportError("需要安装requests: pip install requests")
        
        self._server_url = server_url.rstrip("/")
        self._api_key = api_key
        self._device_id = device_id or self.serial
        
        # 验证API密钥
        if not self._verify_api_key():
            return False
        
        # 注册设备
        self._register_device()
        
        # 启动心跳
        if auto_heartbeat:
            self._start_heartbeat()
        
        self._server_connected = True
        logger.info(f"已连接服务器: {self._server_url}")
        return True
    
    def _verify_api_key(self) -> bool:
        """验证API密钥"""
        try:
            response = requests.post(
                f"{self._server_url}/api/v1/sdk/verify",
                headers={"Authorization": f"Bearer {self._api_key}"},
                json={"device_id": self._device_id},
                timeout=10
            )
            
            if response.status_code == 200:
                logger.info("API密钥验证成功")
                return True
            elif response.status_code == 401:
                logger.error("API密钥无效")
                return False
            else:
                logger.error(f"API验证失败: {response.status_code}")
                return False
        except Exception as e:
            logger.error(f"连接服务器失败: {e}")
            return False
    
    def _register_device(self):
        """向服务器注册设备"""
        try:
            device_info = self.get_device_info()
            response = requests.post(
                f"{self._server_url}/api/v1/sdk/device/register",
                headers={"Authorization": f"Bearer {self._api_key}"},
                json={
                    "device_id": self._device_id,
                    "serial": self.serial,
                    "model": device_info.get("model"),
                    "brand": device_info.get("brand"),
                    "android_version": device_info.get("android_version"),
                    "screen_size": device_info.get("screen_size"),
                    "connection_type": "adb_usb"
                },
                timeout=10
            )
            if response.status_code == 200:
                logger.info(f"设备已注册: {self._device_id}")
        except Exception as e:
            logger.error(f"设备注册失败: {e}")
    
    def _start_heartbeat(self, interval: int = 30):
        """启动心跳线程"""
        if self._heartbeat_running:
            return
        
        self._heartbeat_running = True
        
        def heartbeat_loop():
            while self._heartbeat_running:
                try:
                    self._send_heartbeat()
                except Exception as e:
                    logger.error(f"心跳发送失败: {e}")
                time.sleep(interval)
        
        self._heartbeat_thread = threading.Thread(target=heartbeat_loop, daemon=True)
        self._heartbeat_thread.start()
        logger.info(f"心跳已启动，间隔{interval}秒")
    
    def _stop_heartbeat(self):
        """停止心跳"""
        self._heartbeat_running = False
        if self._heartbeat_thread:
            self._heartbeat_thread.join(timeout=2)
            self._heartbeat_thread = None
    
    def _send_heartbeat(self):
        """发送心跳"""
        try:
            # 获取设备状态
            battery = self.get_battery_level()
            screen_on = self.is_screen_on()
            current_app = self.get_current_app()
            
            response = requests.post(
                f"{self._server_url}/api/v1/sdk/device/heartbeat",
                headers={"Authorization": f"Bearer {self._api_key}"},
                json={
                    "device_id": self._device_id,
                    "status": "online",
                    "battery": battery,
                    "screen_on": screen_on,
                    "current_app": current_app.get("package", ""),
                    "timestamp": time.time()
                },
                timeout=10
            )
            
            # 检查是否有待执行的任务
            if response.status_code == 200:
                data = response.json()
                if data.get("task"):
                    self._handle_task(data["task"])
        except Exception as e:
            logger.debug(f"心跳异常: {e}")
    
    def _handle_task(self, task: dict):
        """处理服务器下发的任务"""
        task_id = task.get("task_id")
        task_type = task.get("type")
        params = task.get("params", {})
        
        logger.info(f"收到任务: {task_id} ({task_type})")
        
        try:
            result = None
            
            # 执行任务
            if task_type == "click":
                self.click(params.get("x"), params.get("y"))
                result = {"success": True}
            elif task_type == "swipe":
                self.swipe(params.get("x1"), params.get("y1"), 
                          params.get("x2"), params.get("y2"))
                result = {"success": True}
            elif task_type == "input":
                self.input_text(params.get("text"))
                result = {"success": True}
            elif task_type == "screenshot":
                data = self.screenshot()
                result = {"success": True, "size": len(data)}
            elif task_type == "start_app":
                self.start_app(params.get("package"))
                result = {"success": True}
            elif task_type == "stop_app":
                self.stop_app(params.get("package"))
                result = {"success": True}
            elif task_type == "click_text":
                found = self.click_text(params.get("text"))
                result = {"success": found}
            elif task_type == "script":
                # 执行自定义脚本
                if self._task_callback:
                    result = self._task_callback(task)
                else:
                    result = {"success": False, "error": "无脚本处理器"}
            else:
                result = {"success": False, "error": f"未知任务类型: {task_type}"}
            
            # 上报结果
            self._report_task_result(task_id, result)
            
        except Exception as e:
            logger.error(f"任务执行失败: {e}")
            self._report_task_result(task_id, {"success": False, "error": str(e)})
    
    def _report_task_result(self, task_id: str, result: dict):
        """上报任务执行结果"""
        try:
            requests.post(
                f"{self._server_url}/api/v1/sdk/task/result",
                headers={"Authorization": f"Bearer {self._api_key}"},
                json={
                    "task_id": task_id,
                    "device_id": self._device_id,
                    "result": result,
                    "timestamp": time.time()
                },
                timeout=10
            )
        except Exception as e:
            logger.error(f"结果上报失败: {e}")
    
    def set_task_callback(self, callback: Callable):
        """
        设置自定义任务回调函数
        
        Args:
            callback: 回调函数，接收task参数，返回result字典
        
        Example:
            >>> def my_handler(task):
            ...     # 处理自定义任务
            ...     return {"success": True, "data": "xxx"}
            >>> device.set_task_callback(my_handler)
        """
        self._task_callback = callback
    
    def report_status(self, status: str, message: str = None, data: dict = None):
        """
        主动上报状态到服务器
        
        Args:
            status: 状态，如 "running", "completed", "error"
            message: 消息
            data: 附加数据
        
        Example:
            >>> device.report_status("running", "正在执行任务")
            >>> device.report_status("completed", "任务完成", {"count": 10})
        """
        if not self._server_connected:
            logger.warning("未连接服务器，无法上报状态")
            return
        
        try:
            requests.post(
                f"{self._server_url}/api/v1/sdk/device/status",
                headers={"Authorization": f"Bearer {self._api_key}"},
                json={
                    "device_id": self._device_id,
                    "status": status,
                    "message": message,
                    "data": data,
                    "timestamp": time.time()
                },
                timeout=10
            )
        except Exception as e:
            logger.error(f"状态上报失败: {e}")
    
    def disconnect_server(self):
        """断开服务器连接"""
        self._stop_heartbeat()
        self._server_connected = False
        logger.info("已断开服务器连接")
    
    @property
    def server_connected(self) -> bool:
        """是否已连接服务器"""
        return self._server_connected
