Files
workphone-sdk/sdk/app/routers/adb.py

273 lines
7.3 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.

"""
工作手机SDK v3.0 - ADB设备路由
直接ADB控制模式支持模拟器和真机
所有端点使用 def非 async defFastAPI 自动在线程池中执行,
避免同步 ADB shell 命令阻塞事件循环。
"""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import Optional
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from services.adb_device import adb_manager
from services.ws_hub import ws_hub
router = APIRouter(prefix="/api/v3/adb", tags=["ADB设备控制"])
async def _ws_execute(serial: str, action: str, params: Optional[dict] = None, timeout: int = 30) -> Optional[dict]:
"""ADB 兼容路由兜底serial 为逻辑 device_id 且 WS 在线时转发到 Agent。"""
if not ws_hub.is_online(serial):
return None
result = await ws_hub.send_command(
serial,
{
"type": "execute",
"data": {
"action": action,
"params": params or {},
},
},
timeout=timeout,
)
code = result.get("code", 200)
return {
"code": code,
"message": result.get("message", "success" if code == 200 else "failed"),
"data": result.get("data", {}),
"channel_used": "websocket/agent",
}
def _raise_not_found():
raise HTTPException(status_code=404, detail="设备不存在")
# ========== 请求模型 ==========
class ClickRequest(BaseModel):
x: int
y: int
class ClickTextRequest(BaseModel):
text: str
timeout: float = 10
class InputRequest(BaseModel):
text: str
clear: bool = True
class SwipeRequest(BaseModel):
direction: str
scale: float = 0.5
class AppRequest(BaseModel):
package: str
class KeyRequest(BaseModel):
key: str
# ========== 设备管理 ==========
@router.get("/devices")
def list_devices():
"""获取所有ADB设备"""
devices = adb_manager.list_devices()
return {"code": 200, "data": devices}
@router.get("/scan")
@router.post("/scan")
def scan_devices():
"""扫描设备(含未授权设备的完整信息)"""
serials = adb_manager.scan_devices()
all_devices = adb_manager.scan_all_devices()
return {
"code": 200,
"data": {
"devices": serials,
"all_devices": all_devices,
"authorized_count": len(serials),
"total_count": len(all_devices),
},
}
@router.get("/devices/{serial}")
async def get_device_info(serial: str):
"""获取设备详情"""
device = adb_manager.get_device(serial)
if not device:
info = ws_hub.get_device_info(serial)
if info:
return {"code": 200, "data": {**info, "channel_used": "websocket/agent"}}
_raise_not_found()
info = device.get_info()
info["status"] = "online" if device.is_online() else "offline"
return {"code": 200, "data": info}
# ========== 基础操作 ==========
@router.post("/devices/{serial}/screenshot")
async def screenshot(serial: str):
"""截图"""
device = adb_manager.get_device(serial)
if not device:
result = await _ws_execute(serial, "screenshot", timeout=12)
if result:
return result
_raise_not_found()
return device.screenshot()
@router.post("/devices/{serial}/click")
async def click(serial: str, req: ClickRequest):
"""点击坐标"""
device = adb_manager.get_device(serial)
if not device:
result = await _ws_execute(serial, "click", {"x": req.x, "y": req.y})
if result:
return result
_raise_not_found()
return device.click(req.x, req.y)
@router.post("/devices/{serial}/click-text")
async def click_text(serial: str, req: ClickTextRequest):
"""点击文字"""
device = adb_manager.get_device(serial)
if not device:
result = await _ws_execute(serial, "click_text", {"text": req.text, "timeout": req.timeout})
if result:
return result
_raise_not_found()
return device.click_text(req.text, req.timeout)
@router.post("/devices/{serial}/input")
async def input_text(serial: str, req: InputRequest):
"""输入文字"""
device = adb_manager.get_device(serial)
if not device:
result = await _ws_execute(serial, "input", {"text": req.text, "clear": req.clear})
if result:
return result
_raise_not_found()
return device.input_text(req.text, req.clear)
@router.post("/devices/{serial}/swipe")
async def swipe(serial: str, req: SwipeRequest):
"""滑动"""
device = adb_manager.get_device(serial)
if not device:
result = await _ws_execute(serial, "swipe", {"direction": req.direction, "scale": req.scale})
if result:
return result
_raise_not_found()
return device.swipe(req.direction, req.scale)
@router.post("/devices/{serial}/key")
async def press_key(serial: str, req: KeyRequest):
"""按键"""
device = adb_manager.get_device(serial)
if not device:
result = await _ws_execute(serial, "press_key", {"key": req.key})
if result:
return result
_raise_not_found()
return device.press_key(req.key)
# ========== APP操作 ==========
@router.post("/devices/{serial}/app/start")
async def start_app(serial: str, req: AppRequest):
"""启动APP"""
device = adb_manager.get_device(serial)
if not device:
result = await _ws_execute(serial, "app_start", {"package": req.package}, timeout=45)
if result:
return result
_raise_not_found()
return device.start_app(req.package)
@router.post("/devices/{serial}/app/stop")
async def stop_app(serial: str, req: AppRequest):
"""停止APP"""
device = adb_manager.get_device(serial)
if not device:
result = await _ws_execute(serial, "app_stop", {"package": req.package}, timeout=45)
if result:
return result
_raise_not_found()
return device.stop_app(req.package)
@router.get("/devices/{serial}/app/current")
async def current_app(serial: str):
"""获取当前APP"""
device = adb_manager.get_device(serial)
if not device:
result = await _ws_execute(serial, "get_foreground", timeout=15)
if result:
return result
_raise_not_found()
return device.current_app()
@router.get("/devices/{serial}/app/list")
def list_apps(serial: str):
"""获取已安装APP"""
device = adb_manager.get_device(serial)
if not device:
raise HTTPException(status_code=404, detail="设备不存在")
return device.installed_apps()
# ========== UI ==========
@router.get("/devices/{serial}/ui-tree")
async def get_ui_tree(serial: str):
"""获取UI树XML 已做 JSON-safe 转义)"""
device = adb_manager.get_device(serial)
if not device:
result = await _ws_execute(serial, "ui_tree", timeout=20)
if result:
xml = result.get("data", {}).get("xml", "")
if isinstance(xml, str):
result["data"]["xml"] = xml.replace("\x00", "").replace("\r", "")
return result
_raise_not_found()
result = device.get_ui_tree()
if isinstance(result, dict) and "data" in result:
xml = result["data"].get("xml", "")
if isinstance(xml, str):
result["data"]["xml"] = xml.replace("\x00", "").replace("\r", "")
return result