feat(android): add native controls and continuous multi-touch
This commit is contained in:
1 parent
3dd96078db
commit
4b12ae7728
70 files changed
+13105
-179
No files matched your search
@@ -113,6 +113,59 @@ class AndroidAdbDriver(BaseDeviceDriver):
|
||||
def screen_size(self) -> tuple[int, int]:
|
||||
return (self._width, self._height)
|
||||
|
||||
@property
|
||||
def native_capabilities(self) -> frozenset[str]:
|
||||
capabilities = getattr(self._ui_adb_client, "native_capabilities", frozenset())
|
||||
return capabilities if isinstance(capabilities, frozenset) else frozenset()
|
||||
|
||||
async def native_request(
|
||||
self, command: str, args: dict[str, Any], *, timeout: float | None = None
|
||||
) -> dict[str, Any]:
|
||||
request = getattr(self._ui_adb_client, "native_request", None)
|
||||
if not callable(request):
|
||||
return await super().native_request(command, args, timeout=timeout)
|
||||
operation = asyncio.create_task(asyncio.to_thread(request, command, args, timeout=timeout))
|
||||
try:
|
||||
return await asyncio.shield(operation)
|
||||
except asyncio.CancelledError:
|
||||
if command == "inspect_control":
|
||||
# A read cannot inject input. Do not let a blocked synchronous
|
||||
# transport or session repair consume the condition's deadline.
|
||||
operation.cancel()
|
||||
raise
|
||||
# Keep the resource locked until the bounded native call finishes. Cancelling
|
||||
# to_thread alone would leave input running after its task had released it.
|
||||
if command == "touch_gesture" and args.get("operation_id"):
|
||||
cancellation = asyncio.create_task(
|
||||
asyncio.to_thread(
|
||||
request,
|
||||
"cancel_gesture",
|
||||
{"operation_id": args["operation_id"]},
|
||||
timeout=2.0,
|
||||
)
|
||||
)
|
||||
while not cancellation.done():
|
||||
try:
|
||||
await asyncio.shield(cancellation)
|
||||
except asyncio.CancelledError:
|
||||
continue
|
||||
except (OSError, RuntimeError, ValueError) as exc:
|
||||
logger.debug(f"Native gesture cancellation failed during cleanup: {exc}")
|
||||
break
|
||||
while not operation.done():
|
||||
try:
|
||||
await asyncio.shield(operation)
|
||||
except asyncio.CancelledError:
|
||||
continue
|
||||
except (OSError, RuntimeError, ValueError):
|
||||
break
|
||||
if not operation.cancelled():
|
||||
try:
|
||||
operation.result()
|
||||
except (OSError, RuntimeError, ValueError) as exc:
|
||||
logger.debug(f"Native operation failed while draining cancelled input: {exc}")
|
||||
raise
|
||||
|
||||
@property
|
||||
def device(self) -> AdbDevice:
|
||||
if self._device is None:
|
||||
|
||||
@@ -68,6 +68,20 @@ class ScreenData(BaseModel):
|
||||
class BaseDeviceDriver(ABC):
|
||||
"""Abstract Base Class for mobile device and emulator drivers."""
|
||||
|
||||
@property
|
||||
def native_capabilities(self) -> frozenset[str]:
|
||||
return frozenset()
|
||||
|
||||
async def native_request(
|
||||
self, command: str, args: dict[str, Any], *, timeout: float | None = None
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"success": False,
|
||||
"code": "UNSUPPORTED",
|
||||
"execution": "not_started",
|
||||
"error": "This device backend does not implement native Android actions.",
|
||||
}
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def device_id(self) -> str:
|
||||
|
||||
Reference in new issue
Block a user