"""Versioned mobile semantics; no model, device or frozen codec changes on import.""" import math from dataclasses import asdict, dataclass LEGACY_PROTOCOL = "gui-owl-unified133-desktop-android-historybins-v1" PROTOCOL = "gui-owl-unified133-mobile-semantics-v2" SCHEMA = "unified133-mobile-subgoal-sft-v2" DIRECTIONS = ("up", "down", "left", "right") ANDROID_WORLD_REVISION = "55ddaf5edc6f6c25673aed343907d5b9dca66f84" @dataclass(frozen=True) class MobileExecutionConfig: """Runtime policy, never a claim about missing demonstration durations. AndroidWorld uses 1 second for wait and long press. Its directional scroll leaves duration to Android's input command. Coordinate swipe stays on the frozen executor's explicit 800 ms path. """ protocol: str = PROTOCOL wait_seconds: float = 1.0 long_press_seconds: float = 1.0 scroll_duration_ms: int | None = None scroll_geometry: str = "androidworld-screen-center-to-edge" reference_revision: str = ANDROID_WORLD_REVISION def __post_init__(self): if self.protocol != PROTOCOL: raise ValueError("execution_protocol_mismatch") for value in (self.wait_seconds, self.long_press_seconds): if ( type(value) not in (int, float) or not math.isfinite(value) or not 0 < value <= 999.99 ): raise ValueError("invalid_execution_default_duration") if self.scroll_duration_ms is not None and ( type(self.scroll_duration_ms) is not int or not 1 <= self.scroll_duration_ms <= 999990 ): raise ValueError("invalid_scroll_duration") if self.scroll_geometry != "androidworld-screen-center-to-edge": raise ValueError("unknown_scroll_geometry") if self.reference_revision != ANDROID_WORLD_REVISION: raise ValueError("execution_reference_mismatch") def as_dict(self): return asdict(self) def direction_scroll_argv(direction, size, config): """Small argv-only adapter for pinned AndroidWorld execute_adb_action(scroll). Apache-2.0 reference: third_party/android_world_mobile_reference. The full executor imports AndroidEnv/ADB services and also changes text-entry semantics; only this GUI primitive is adapted. Endpoints are runtime geometry, not labels. """ if direction not in DIRECTIONS: raise ValueError("invalid_scroll_direction") if len(size) != 2 or any(type(v) is not int or v <= 0 for v in size): raise ValueError("invalid_screen_size") width, height = size start = [width // 2, height // 2] ends = { "down": [width // 2, 0], "up": [width // 2, height], "right": [0, height // 2], "left": [width, height // 2], } argv = ["input", "swipe", *map(str, start + ends[direction])] if config.scroll_duration_ms is not None: argv.append(str(config.scroll_duration_ms)) return argv