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

141 lines
4.0 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.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Android device connection helpers shared by Airtest, DroidBot and ADB wrappers.
"""
from __future__ import annotations
import logging
import re
import subprocess
from typing import Any, Dict, Optional
from urllib.parse import urlencode
logger = logging.getLogger(__name__)
WIRELESS_SERIAL_RE = re.compile(r"^[A-Za-z0-9_.-]+:\d+$")
def _clean(value: Any) -> str:
return str(value or "").strip()
def _load_config_if_needed(config: Optional[Dict[str, Any]]) -> Dict[str, Any]:
if config is not None:
return config
try:
from config_loader import load_config
return load_config()
except Exception as exc:
logger.debug(f"加载 Android 设备配置失败,使用默认 ADB 设备: {exc}")
return {}
def _is_emulator(config: Dict[str, Any]) -> bool:
return bool(config.get("IS_EMULATOR", True))
def is_wireless_serial(serial: Optional[str]) -> bool:
return bool(WIRELESS_SERIAL_RE.match(_clean(serial)))
def get_android_device_serial(config: Optional[Dict[str, Any]] = None) -> Optional[str]:
"""Return the configured adb serial, or None to let adb auto-select."""
config = _load_config_if_needed(config)
serial = (
_clean(config.get("ANDROID_DEVICE_SERIAL"))
or _clean(config.get("PHYSICAL_DEVICE_SERIAL"))
or _clean(config.get("PHYSICAL_ADB_SERIAL"))
or _clean(config.get("ADB_DEVICE_SERIAL"))
)
if serial:
return serial
if _is_emulator(config):
return None
ip = (
_clean(config.get("PHYSICAL_DEVICE_IP"))
or _clean(config.get("PHYSICAL_ADB_IP"))
or _clean(config.get("ANDROID_DEVICE_IP"))
or _clean(config.get("ADB_DEVICE_IP"))
)
if not ip:
return None
if ":" in ip:
return ip
port = (
_clean(config.get("PHYSICAL_ADB_PORT"))
or _clean(config.get("ANDROID_ADB_PORT"))
or "5555"
)
return f"{ip}:{port}"
def ensure_android_wireless_connected(
config: Optional[Dict[str, Any]] = None,
serial: Optional[str] = None,
timeout: int = 15,
) -> bool:
"""Connect adb to a configured wireless device when serial is ip:port."""
config = _load_config_if_needed(config)
serial = _clean(serial) or get_android_device_serial(config)
if not is_wireless_serial(serial):
return True
try:
result = subprocess.run(
["adb", "connect", serial],
capture_output=True,
encoding="utf-8",
errors="ignore",
timeout=timeout,
)
except FileNotFoundError:
logger.error("未找到 adb可执行文件不可用")
return False
except subprocess.TimeoutExpired:
logger.warning(f"无线 ADB 连接超时: {serial}")
return False
except Exception as exc:
logger.warning(f"无线 ADB 连接异常: {serial} - {exc}")
return False
output = (result.stdout or result.stderr or "").strip()
if result.returncode == 0 and (
"connected" in output.lower() or "already connected" in output.lower()
):
logger.info(f"无线 ADB 已连接: {serial}")
return True
logger.warning(f"无线 ADB 连接失败: {serial} - {output or result.returncode}")
return False
def build_airtest_android_uri(
config: Optional[Dict[str, Any]] = None,
*,
serial: Optional[str] = None,
**params: Any,
) -> str:
"""Build an Airtest Android URI pinned to the configured adb serial."""
config = _load_config_if_needed(config)
serial = _clean(serial) or get_android_device_serial(config)
host = _clean(config.get("AIRTEST_ADB_HOST")) or "127.0.0.1:5037"
clean_params = {
key: value
for key, value in params.items()
if value is not None and _clean(value) != ""
}
query = f"?{urlencode(clean_params)}" if clean_params else ""
if serial:
return f"Android://{host}/{serial}{query}"
return f"Android:///{query}"