autool/utils_android/Manager/physical_device_manager.py
2026-06-17 19:44:18 +08:00

139 lines
5.2 KiB
Python
Raw Permalink 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.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Android 真机管理模块。
"""
from __future__ import annotations
import logging
import subprocess
import time
from typing import Optional
from utils_android.device_config import ensure_android_wireless_connected, get_android_device_serial
logger = logging.getLogger(__name__)
class PhysicalDeviceManager:
"""真机设备管理器。
支持通过配置指定 USB serial 或无线调试 ip:port。
"""
def __init__(self, config):
self.config = config
self.serial = get_android_device_serial(config)
self.boot_timeout_seconds = int(self.config.get("PHYSICAL_BOOT_TIMEOUT_SECONDS", 180))
self.boot_poll_interval_seconds = int(self.config.get("PHYSICAL_BOOT_POLL_INTERVAL_SECONDS", 5))
if self.serial:
logger.info(f"真机 ADB 目标: {self.serial}")
@property
def device_type(self) -> str:
return "physical"
def _run_adb(self, args, timeout: int = 15) -> subprocess.CompletedProcess[str]:
cmd = ["adb"]
if self.serial:
cmd.extend(["-s", self.serial])
cmd.extend(args)
return subprocess.run(
cmd,
capture_output=True,
encoding="utf-8",
errors="ignore",
timeout=timeout,
)
def _read_output(self, result: subprocess.CompletedProcess[str]) -> str:
return (result.stdout or result.stderr or "").strip()
def _ensure_connected(self) -> bool:
return ensure_android_wireless_connected(self.config, self.serial)
def _wait_for_boot_complete(self, timeout_seconds: Optional[int] = None) -> bool:
deadline = time.time() + float(timeout_seconds or self.boot_timeout_seconds)
while time.time() < deadline:
try:
self._ensure_connected()
state_result = self._run_adb(["get-state"], timeout=10)
state = self._read_output(state_result).lower()
if state_result.returncode != 0 or state != "device":
time.sleep(self.boot_poll_interval_seconds)
continue
boot_result = self._run_adb(["shell", "getprop", "sys.boot_completed"], timeout=10)
boot_completed = self._read_output(boot_result)
if boot_result.returncode == 0 and boot_completed == "1":
sdk_result = self._run_adb(["shell", "getprop", "ro.build.version.sdk"], timeout=10)
if sdk_result.returncode == 0 and self._read_output(sdk_result):
logger.info("真机重启完成ADB 已恢复可用")
return True
except subprocess.TimeoutExpired:
logger.warning("等待真机启动完成超时,继续轮询")
except Exception as exc:
logger.warning(f"等待真机启动完成时出现异常: {exc}")
time.sleep(self.boot_poll_interval_seconds)
logger.error("等待真机启动完成超时")
return False
def check_status(self) -> bool:
try:
if not self._ensure_connected():
return False
state_result = self._run_adb(["get-state"], timeout=10)
state = self._read_output(state_result).lower()
if state_result.returncode != 0 or state != "device":
logger.warning(f"真机 ADB 状态异常: {self._read_output(state_result) or state or 'unknown'}")
return False
verify_result = self._run_adb(["shell", "getprop", "ro.build.version.sdk"], timeout=10)
if verify_result.returncode == 0 and self._read_output(verify_result):
return True
logger.warning(f"真机 ADB 校验失败: {self._read_output(verify_result)}")
return False
except subprocess.TimeoutExpired:
logger.warning("检查真机状态超时")
return False
except FileNotFoundError:
logger.error("未找到 adb可执行文件不可用")
return False
except Exception as exc:
logger.warning(f"检查真机状态失败: {exc}")
return False
def ensure_running(self) -> tuple[bool, bool]:
return (self.check_status(), False)
def restart(self) -> bool:
logger.info("正在重启真机...")
try:
result = self._run_adb(["reboot"], timeout=20)
if result.returncode != 0:
logger.error(f"真机重启命令失败: {self._read_output(result)}")
return False
time.sleep(3)
return self._wait_for_boot_complete()
except subprocess.TimeoutExpired:
logger.warning("adb reboot 等待超时,继续等待设备上线")
return self._wait_for_boot_complete()
except Exception as exc:
logger.error(f"真机重启失败: {exc}")
return False
def recover(self) -> bool:
logger.info("真机模式不执行 recover直接跳过")
return True
def recover_from_frozen(self) -> bool:
logger.warning("检测到真机可能卡死,尝试重启恢复")
return self.restart()
def is_player_frozen(self) -> bool:
return not self.check_status()