androidctl 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. androidctl/__init__.py +5 -0
  2. androidctl/__main__.py +4 -0
  3. androidctl/_version.py +1 -0
  4. androidctl/app.py +73 -0
  5. androidctl/cli_options.py +27 -0
  6. androidctl/command_payloads.py +264 -0
  7. androidctl/command_views.py +157 -0
  8. androidctl/commands/__init__.py +1 -0
  9. androidctl/commands/actions.py +236 -0
  10. androidctl/commands/adb_wireless.py +157 -0
  11. androidctl/commands/close.py +30 -0
  12. androidctl/commands/connect.py +69 -0
  13. androidctl/commands/execute.py +179 -0
  14. androidctl/commands/list_apps.py +26 -0
  15. androidctl/commands/observe.py +26 -0
  16. androidctl/commands/open.py +41 -0
  17. androidctl/commands/plumbing.py +58 -0
  18. androidctl/commands/run_pipeline.py +307 -0
  19. androidctl/commands/screenshot.py +29 -0
  20. androidctl/commands/setup.py +301 -0
  21. androidctl/commands/wait.py +60 -0
  22. androidctl/daemon/__init__.py +1 -0
  23. androidctl/daemon/client.py +348 -0
  24. androidctl/daemon/discovery.py +190 -0
  25. androidctl/daemon/launcher.py +26 -0
  26. androidctl/daemon/owner.py +349 -0
  27. androidctl/errors/__init__.py +1 -0
  28. androidctl/errors/mapping.py +149 -0
  29. androidctl/errors/models.py +16 -0
  30. androidctl/exit_codes.py +8 -0
  31. androidctl/output.py +147 -0
  32. androidctl/parsing/__init__.py +1 -0
  33. androidctl/parsing/duration.py +17 -0
  34. androidctl/parsing/open_target.py +51 -0
  35. androidctl/parsing/refs.py +12 -0
  36. androidctl/parsing/screen_id.py +10 -0
  37. androidctl/parsing/wait.py +70 -0
  38. androidctl/renderers/__init__.py +110 -0
  39. androidctl/renderers/_paths.py +109 -0
  40. androidctl/renderers/xml.py +234 -0
  41. androidctl/renderers/xml_projection.py +732 -0
  42. androidctl/resources/__init__.py +1 -0
  43. androidctl/resources/androidctl-agent-0.1.0-release.apk +0 -0
  44. androidctl/setup/__init__.py +1 -0
  45. androidctl/setup/accessibility.py +159 -0
  46. androidctl/setup/adb.py +586 -0
  47. androidctl/setup/apk_resource.py +29 -0
  48. androidctl/setup/pairing.py +70 -0
  49. androidctl/setup/verify.py +175 -0
  50. androidctl/workspace/__init__.py +3 -0
  51. androidctl/workspace/resolve.py +27 -0
  52. androidctl-0.1.0.dist-info/METADATA +217 -0
  53. androidctl-0.1.0.dist-info/RECORD +187 -0
  54. androidctl-0.1.0.dist-info/WHEEL +5 -0
  55. androidctl-0.1.0.dist-info/entry_points.txt +3 -0
  56. androidctl-0.1.0.dist-info/licenses/LICENSE +674 -0
  57. androidctl-0.1.0.dist-info/top_level.txt +3 -0
  58. androidctl_contracts/__init__.py +55 -0
  59. androidctl_contracts/_version.py +1 -0
  60. androidctl_contracts/_wire_helpers.py +31 -0
  61. androidctl_contracts/base.py +142 -0
  62. androidctl_contracts/command_catalog.py +414 -0
  63. androidctl_contracts/command_results.py +630 -0
  64. androidctl_contracts/daemon_api.py +335 -0
  65. androidctl_contracts/errors.py +44 -0
  66. androidctl_contracts/paths.py +5 -0
  67. androidctl_contracts/public_screen.py +579 -0
  68. androidctl_contracts/user_state.py +23 -0
  69. androidctl_contracts/vocabulary.py +82 -0
  70. androidctld/__init__.py +5 -0
  71. androidctld/__main__.py +63 -0
  72. androidctld/_version.py +1 -0
  73. androidctld/actions/__init__.py +1 -0
  74. androidctld/actions/action_target.py +142 -0
  75. androidctld/actions/capabilities.py +539 -0
  76. androidctld/actions/executor.py +894 -0
  77. androidctld/actions/focus_confirmation.py +177 -0
  78. androidctld/actions/focused_input_admissibility.py +120 -0
  79. androidctld/actions/fresh_current.py +176 -0
  80. androidctld/actions/postconditions.py +473 -0
  81. androidctld/actions/repair.py +101 -0
  82. androidctld/actions/request_builder.py +204 -0
  83. androidctld/actions/settle.py +146 -0
  84. androidctld/actions/submit_confirmation.py +211 -0
  85. androidctld/actions/submit_routing.py +311 -0
  86. androidctld/actions/type_confirmation.py +257 -0
  87. androidctld/app_targets.py +71 -0
  88. androidctld/artifacts/__init__.py +1 -0
  89. androidctld/artifacts/models.py +26 -0
  90. androidctld/artifacts/screen_lookup.py +241 -0
  91. androidctld/artifacts/screen_payloads.py +109 -0
  92. androidctld/artifacts/writer.py +286 -0
  93. androidctld/auth/__init__.py +1 -0
  94. androidctld/auth/active_registry.py +266 -0
  95. androidctld/auth/secret_files.py +52 -0
  96. androidctld/auth/token_store.py +59 -0
  97. androidctld/commands/__init__.py +1 -0
  98. androidctld/commands/assembly.py +231 -0
  99. androidctld/commands/command_models.py +254 -0
  100. androidctld/commands/dispatch.py +99 -0
  101. androidctld/commands/executor.py +31 -0
  102. androidctld/commands/from_boundary.py +175 -0
  103. androidctld/commands/handlers/__init__.py +15 -0
  104. androidctld/commands/handlers/action.py +439 -0
  105. androidctld/commands/handlers/connect.py +94 -0
  106. androidctld/commands/handlers/list_apps.py +215 -0
  107. androidctld/commands/handlers/observe.py +121 -0
  108. androidctld/commands/handlers/screenshot.py +105 -0
  109. androidctld/commands/handlers/wait.py +286 -0
  110. androidctld/commands/models.py +65 -0
  111. androidctld/commands/open_targets.py +56 -0
  112. androidctld/commands/orchestration.py +353 -0
  113. androidctld/commands/registry.py +116 -0
  114. androidctld/commands/result_builders.py +40 -0
  115. androidctld/commands/result_models.py +555 -0
  116. androidctld/commands/results.py +108 -0
  117. androidctld/commands/semantic_command_names.py +17 -0
  118. androidctld/commands/semantic_error_mapping.py +93 -0
  119. androidctld/commands/semantic_truth.py +135 -0
  120. androidctld/commands/service.py +67 -0
  121. androidctld/config.py +75 -0
  122. androidctld/daemon/__init__.py +1 -0
  123. androidctld/daemon/active_slot.py +326 -0
  124. androidctld/daemon/envelope.py +30 -0
  125. androidctld/daemon/http_host.py +123 -0
  126. androidctld/daemon/ingress.py +112 -0
  127. androidctld/daemon/ownership_probe.py +204 -0
  128. androidctld/daemon/server.py +286 -0
  129. androidctld/daemon/service.py +99 -0
  130. androidctld/device/__init__.py +1 -0
  131. androidctld/device/action_models.py +154 -0
  132. androidctld/device/action_serialization.py +121 -0
  133. androidctld/device/adapters.py +220 -0
  134. androidctld/device/bootstrap.py +153 -0
  135. androidctld/device/connectors.py +231 -0
  136. androidctld/device/errors.py +100 -0
  137. androidctld/device/interfaces.py +58 -0
  138. androidctld/device/parsing.py +320 -0
  139. androidctld/device/rpc.py +483 -0
  140. androidctld/device/schema.py +114 -0
  141. androidctld/device/types.py +161 -0
  142. androidctld/errors/__init__.py +94 -0
  143. androidctld/logging/__init__.py +22 -0
  144. androidctld/observation.py +98 -0
  145. androidctld/protocol.py +53 -0
  146. androidctld/refs/__init__.py +1 -0
  147. androidctld/refs/models.py +54 -0
  148. androidctld/refs/repair.py +284 -0
  149. androidctld/refs/service.py +422 -0
  150. androidctld/rendering/__init__.py +1 -0
  151. androidctld/rendering/screen_xml.py +256 -0
  152. androidctld/runtime/__init__.py +21 -0
  153. androidctld/runtime/kernel.py +548 -0
  154. androidctld/runtime/lifecycle.py +19 -0
  155. androidctld/runtime/models.py +48 -0
  156. androidctld/runtime/screen_state.py +117 -0
  157. androidctld/runtime/state_repo.py +70 -0
  158. androidctld/runtime/store.py +76 -0
  159. androidctld/runtime_policy.py +127 -0
  160. androidctld/schema/__init__.py +5 -0
  161. androidctld/schema/base.py +132 -0
  162. androidctld/schema/core.py +35 -0
  163. androidctld/schema/daemon_api.py +108 -0
  164. androidctld/schema/persistence.py +161 -0
  165. androidctld/schema/persistence_io.py +41 -0
  166. androidctld/schema/validation_errors.py +309 -0
  167. androidctld/semantics/__init__.py +1 -0
  168. androidctld/semantics/compiler.py +610 -0
  169. androidctld/semantics/continuity.py +107 -0
  170. androidctld/semantics/labels.py +252 -0
  171. androidctld/semantics/models.py +25 -0
  172. androidctld/semantics/policy.py +23 -0
  173. androidctld/semantics/public_models.py +123 -0
  174. androidctld/semantics/registries.py +13 -0
  175. androidctld/semantics/submit_refs.py +417 -0
  176. androidctld/semantics/surface.py +254 -0
  177. androidctld/semantics/targets.py +167 -0
  178. androidctld/snapshots/__init__.py +1 -0
  179. androidctld/snapshots/models.py +219 -0
  180. androidctld/snapshots/refresh.py +273 -0
  181. androidctld/snapshots/schema.py +74 -0
  182. androidctld/snapshots/service.py +138 -0
  183. androidctld/text_equivalence.py +67 -0
  184. androidctld/waits/__init__.py +1 -0
  185. androidctld/waits/evaluators.py +216 -0
  186. androidctld/waits/loop.py +305 -0
  187. androidctld/waits/matcher.py +41 -0
@@ -0,0 +1,256 @@
1
+ """Standalone public screen XML rendering owned by the daemon."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from xml.etree.ElementTree import Element, SubElement, tostring
7
+
8
+ from androidctl_contracts.public_screen import (
9
+ BLOCKING_GROUP_NAMES,
10
+ PUBLIC_GROUP_NAMES,
11
+ PUBLIC_NODE_ROLE_VALUES,
12
+ BlockingGroupName,
13
+ PublicGroupName,
14
+ )
15
+ from androidctld.semantics.public_models import (
16
+ OmittedEntry,
17
+ PublicApp,
18
+ PublicFocus,
19
+ PublicGroup,
20
+ PublicNode,
21
+ PublicScreen,
22
+ PublicSurface,
23
+ TransientItem,
24
+ VisibleWindow,
25
+ )
26
+
27
+ XmlAttrs = dict[str, str]
28
+
29
+ _APP_ATTRS = (
30
+ "packageName",
31
+ "activityName",
32
+ "requestedPackageName",
33
+ "resolvedPackageName",
34
+ "matchType",
35
+ )
36
+ _FOCUS_ATTRS = ("inputRef",)
37
+ _VISIBLE_WINDOW_ATTRS = ("windowRef", "role", "focused", "blocking")
38
+ _BLOCKING_GROUPS: set[BlockingGroupName] = set(BLOCKING_GROUP_NAMES)
39
+ _NODE_ROLE_TAGS = frozenset(PUBLIC_NODE_ROLE_VALUES)
40
+
41
+
42
+ def render_screen_xml(screen: PublicScreen) -> str:
43
+ """Render a standalone public ``<screen>`` artifact."""
44
+
45
+ root = Element("screen", {"screenId": screen.screen_id})
46
+ _append_app(root, screen.app)
47
+ _append_surface(root, screen.surface)
48
+ _append_groups(root, screen.groups, blocking_group=screen.surface.blocking_group)
49
+ _append_omitted(root, screen.omitted)
50
+ _append_visible_windows(root, screen.visible_windows)
51
+ _append_transient(root, screen.transient)
52
+ return tostring(root, encoding="unicode", short_empty_elements=True)
53
+
54
+
55
+ def _append_app(parent: Element, app: PublicApp) -> None:
56
+ attrs = _non_empty_attrs(
57
+ {
58
+ "packageName": app.package_name,
59
+ "activityName": app.activity_name,
60
+ "requestedPackageName": app.requested_package_name,
61
+ "resolvedPackageName": app.resolved_package_name,
62
+ "matchType": app.match_type,
63
+ },
64
+ _APP_ATTRS,
65
+ )
66
+ SubElement(parent, "app", attrs)
67
+
68
+
69
+ def _append_surface(parent: Element, surface: PublicSurface) -> None:
70
+ attrs = {"keyboardVisible": _bool_string(surface.keyboard_visible)}
71
+ if surface.blocking_group in _BLOCKING_GROUPS:
72
+ attrs["blockingGroup"] = surface.blocking_group
73
+ surface_elem = SubElement(parent, "surface", attrs)
74
+ _append_focus(surface_elem, surface.focus)
75
+
76
+
77
+ def _append_focus(parent: Element, focus: PublicFocus) -> None:
78
+ SubElement(
79
+ parent,
80
+ "focus",
81
+ _non_empty_attrs({"inputRef": focus.input_ref}, _FOCUS_ATTRS),
82
+ )
83
+
84
+
85
+ def _append_groups(
86
+ parent: Element,
87
+ groups: tuple[PublicGroup, ...],
88
+ *,
89
+ blocking_group: BlockingGroupName | None,
90
+ ) -> None:
91
+ groups_elem = SubElement(parent, "groups")
92
+ groups_by_name = {group.name: group for group in groups}
93
+ for group_name in _group_order(blocking_group):
94
+ group_elem = SubElement(groups_elem, group_name)
95
+ group = groups_by_name.get(group_name)
96
+ if group is None:
97
+ continue
98
+ for node in group.nodes:
99
+ _append_group_item(group_elem, node)
100
+
101
+
102
+ def _append_group_item(parent: Element, node: PublicNode) -> None:
103
+ if node.kind == "text":
104
+ text_elem = SubElement(parent, "literal", _text_attrs(node))
105
+ text_elem.text = node.text
106
+ return
107
+
108
+ tag = _node_role_tag(node.role)
109
+ node_elem = SubElement(parent, tag, _node_attrs(node))
110
+ for child in node.children:
111
+ _append_group_item(node_elem, child)
112
+
113
+
114
+ def _append_omitted(parent: Element, omitted: tuple[OmittedEntry, ...]) -> None:
115
+ omitted_elem = SubElement(parent, "omitted")
116
+ for item in omitted:
117
+ attrs: XmlAttrs = {
118
+ "group": item.group,
119
+ "reason": item.reason,
120
+ }
121
+ if item.count is not None:
122
+ attrs["count"] = str(item.count)
123
+ SubElement(omitted_elem, "entry", attrs)
124
+
125
+
126
+ def _append_visible_windows(
127
+ parent: Element,
128
+ visible_windows: tuple[VisibleWindow, ...],
129
+ ) -> None:
130
+ windows_elem = SubElement(parent, "visibleWindows")
131
+ for window in visible_windows:
132
+ SubElement(
133
+ windows_elem,
134
+ "window",
135
+ _attrs(
136
+ {
137
+ "windowRef": window.window_ref,
138
+ "role": window.role,
139
+ "focused": window.focused,
140
+ "blocking": window.blocking,
141
+ },
142
+ _VISIBLE_WINDOW_ATTRS,
143
+ ),
144
+ )
145
+
146
+
147
+ def _append_transient(parent: Element, transient: tuple[TransientItem, ...]) -> None:
148
+ transient_elem = SubElement(parent, "transient")
149
+ for item in transient:
150
+ attrs: XmlAttrs = {}
151
+ if item.kind is not None:
152
+ attrs["kind"] = item.kind
153
+ SubElement(transient_elem, "item", attrs).text = item.text
154
+
155
+
156
+ def _node_attrs(node: PublicNode) -> XmlAttrs:
157
+ attrs: XmlAttrs = {}
158
+ if node.ref is not None:
159
+ attrs["ref"] = node.ref
160
+ if node.ref is not None:
161
+ attrs.update(
162
+ _non_empty_sequence_attrs(
163
+ {
164
+ "actions": node.actions,
165
+ "state": node.state,
166
+ },
167
+ ("actions", "state"),
168
+ )
169
+ )
170
+ if node.scroll_directions:
171
+ attrs["scrollDirections"] = _sequence_attr(node.scroll_directions)
172
+ if node.submit_refs:
173
+ attrs["submitRefs"] = _sequence_attr(node.submit_refs)
174
+ attrs.update(_text_attrs(node))
175
+ return attrs
176
+
177
+
178
+ def _text_attrs(node: PublicNode) -> XmlAttrs:
179
+ attrs: XmlAttrs = {}
180
+ if node.origin is not None:
181
+ attrs["origin"] = node.origin
182
+ if node.window_ref is not None:
183
+ attrs["windowRef"] = node.window_ref
184
+ if node.ambiguity is not None:
185
+ attrs["ambiguity"] = node.ambiguity
186
+ if node.kind != "text" and node.label:
187
+ attrs["label"] = node.label
188
+ if node.within is not None:
189
+ attrs["within"] = node.within
190
+ if node.value is not None:
191
+ attrs["value"] = node.value
192
+ return attrs
193
+
194
+
195
+ def _group_order(
196
+ blocking_group: BlockingGroupName | None,
197
+ ) -> tuple[PublicGroupName, ...]:
198
+ if blocking_group not in _BLOCKING_GROUPS:
199
+ return PUBLIC_GROUP_NAMES
200
+ return (
201
+ blocking_group,
202
+ *(
203
+ group_name
204
+ for group_name in PUBLIC_GROUP_NAMES
205
+ if group_name != blocking_group
206
+ ),
207
+ )
208
+
209
+
210
+ def _attrs(values: dict[str, object], keys: Sequence[str]) -> XmlAttrs:
211
+ attrs: XmlAttrs = {}
212
+ for key in keys:
213
+ value = values.get(key)
214
+ if value is None:
215
+ continue
216
+ if isinstance(value, bool):
217
+ attrs[key] = _bool_string(value)
218
+ elif isinstance(value, Sequence) and not isinstance(value, str):
219
+ attrs[key] = _sequence_attr(value)
220
+ else:
221
+ attrs[key] = str(value)
222
+ return attrs
223
+
224
+
225
+ def _non_empty_attrs(values: dict[str, object], keys: Sequence[str]) -> XmlAttrs:
226
+ return {
227
+ key: str(value)
228
+ for key in keys
229
+ if isinstance((value := values.get(key)), str) and value
230
+ }
231
+
232
+
233
+ def _non_empty_sequence_attrs(
234
+ values: dict[str, object],
235
+ keys: Sequence[str],
236
+ ) -> XmlAttrs:
237
+ attrs: XmlAttrs = {}
238
+ for key in keys:
239
+ value = values.get(key)
240
+ if isinstance(value, Sequence) and not isinstance(value, str) and value:
241
+ attrs[key] = _sequence_attr(value)
242
+ return attrs
243
+
244
+
245
+ def _sequence_attr(values: Sequence[object]) -> str:
246
+ return " ".join(str(item) for item in values)
247
+
248
+
249
+ def _bool_string(value: bool) -> str:
250
+ return "true" if value else "false"
251
+
252
+
253
+ def _node_role_tag(role: str) -> str:
254
+ if role not in _NODE_ROLE_TAGS:
255
+ raise ValueError(f"unsupported public node role for XML tag: {role}")
256
+ return role
@@ -0,0 +1,21 @@
1
+ """Workspace-scoped runtime package."""
2
+
3
+ from androidctld.runtime.kernel import RuntimeKernel
4
+ from androidctld.runtime.lifecycle import RuntimeLifecycleLease, capture_lifecycle_lease
5
+ from androidctld.runtime.models import (
6
+ ScreenState,
7
+ WorkspaceRuntime,
8
+ )
9
+ from androidctld.runtime.state_repo import RuntimeStateRepository
10
+ from androidctld.runtime.store import RuntimeSerialCommandBusyError, RuntimeStore
11
+
12
+ __all__ = [
13
+ "RuntimeKernel",
14
+ "RuntimeLifecycleLease",
15
+ "RuntimeSerialCommandBusyError",
16
+ "RuntimeStateRepository",
17
+ "RuntimeStore",
18
+ "ScreenState",
19
+ "WorkspaceRuntime",
20
+ "capture_lifecycle_lease",
21
+ ]