Skip to content

Commit e3c4abe

Browse files
Merge pull request #251 from askui/feat/android-uiautomator-hierarchy-tool
feat(android): add UIAutomator hierarchy dump, parsing, and agent tool
2 parents c923db8 + 222f8a9 commit e3c4abe

8 files changed

Lines changed: 339 additions & 110 deletions

File tree

pdm.lock

Lines changed: 6 additions & 57 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/askui/tools/android/agent_os.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from PIL import Image
55

6+
from askui.tools.android.uiautomator_hierarchy import UIElementCollection
7+
68
ANDROID_KEY = Literal[ # pylint: disable=C0103
79
"HOME",
810
"BACK",
@@ -493,3 +495,10 @@ def pull(self, remote_path: str, local_path: str) -> None:
493495
Pulls a file from the device.
494496
"""
495497
raise NotImplementedError
498+
499+
@abstractmethod
500+
def get_ui_elements(self) -> UIElementCollection:
501+
"""
502+
Gets the UI elements.
503+
"""
504+
raise NotImplementedError

src/askui/tools/android/agent_os_facade.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from askui.models.shared.tool_tags import ToolTags
66
from askui.tools.android.agent_os import ANDROID_KEY, AndroidAgentOs, AndroidDisplay
7+
from askui.tools.android.uiautomator_hierarchy import UIElementCollection
78
from askui.utils.image_utils import scale_coordinates, scale_image_to_fit
89

910

@@ -36,33 +37,38 @@ def screenshot(self) -> Image.Image:
3637
self._target_resolution,
3738
)
3839

39-
def _scale_coordinates_back(self, x: int, y: int) -> Tuple[int, int]:
40+
def _scale_coordinates(
41+
self,
42+
x: int,
43+
y: int,
44+
from_agent: bool = True,
45+
) -> Tuple[int, int]:
4046
if self._real_screen_resolution is None:
4147
self._real_screen_resolution = self._agent_os.screenshot().size
4248

4349
return scale_coordinates(
4450
(x, y),
4551
self._real_screen_resolution,
4652
self._target_resolution,
47-
inverse=True,
53+
inverse=from_agent,
4854
)
4955

5056
def tap(self, x: int, y: int) -> None:
51-
x, y = self._scale_coordinates_back(x, y)
57+
x, y = self._scale_coordinates(x, y)
5258
self._agent_os.tap(x, y)
5359

5460
def swipe(
5561
self, x1: int, y1: int, x2: int, y2: int, duration_in_ms: int = 1000
5662
) -> None:
57-
x1, y1 = self._scale_coordinates_back(x1, y1)
58-
x2, y2 = self._scale_coordinates_back(x2, y2)
63+
x1, y1 = self._scale_coordinates(x1, y1)
64+
x2, y2 = self._scale_coordinates(x2, y2)
5965
self._agent_os.swipe(x1, y1, x2, y2, duration_in_ms)
6066

6167
def drag_and_drop(
6268
self, x1: int, y1: int, x2: int, y2: int, duration_in_ms: int = 1000
6369
) -> None:
64-
x1, y1 = self._scale_coordinates_back(x1, y1)
65-
x2, y2 = self._scale_coordinates_back(x2, y2)
70+
x1, y1 = self._scale_coordinates(x1, y1)
71+
x2, y2 = self._scale_coordinates(x2, y2)
6672
self._agent_os.drag_and_drop(x1, y1, x2, y2, duration_in_ms)
6773

6874
def type(self, text: str) -> None:
@@ -121,3 +127,17 @@ def push(self, local_path: str, remote_path: str) -> None:
121127

122128
def pull(self, remote_path: str, local_path: str) -> None:
123129
self._agent_os.pull(remote_path, local_path)
130+
131+
def get_ui_elements(self) -> UIElementCollection:
132+
ui_elemet_collection = self._agent_os.get_ui_elements()
133+
for element in ui_elemet_collection:
134+
if element.center is None:
135+
continue
136+
element.set_center(
137+
self._scale_coordinates(
138+
x=element.center[0],
139+
y=element.center[1],
140+
from_agent=False,
141+
)
142+
)
143+
return ui_elemet_collection

0 commit comments

Comments
 (0)