/** * AndroidDriver — GuiDriver over adb against a running Android EMULATOR. * * Ported mechanism-for-mechanism from mirasim's simulator android provider, keeping the * lessons that were each earned by a measured failure: * * - Structural reads via `uiautomator dump`: on Android the tree is both cheaper AND * more precise than pixels — the dump names every element with exact bounds. * - Android has exactly ONE UiAutomation slot. When an accessibility service or an * instrumentation harness holds it, `uiautomator dump` reports `Killed` and no amount * of retrying frees it — the honest answer is a failure naming the cause and the fix, * never a retry loop. (A still-ANIMATING screen is the other, transient dump failure; * three short attempts absorb that one.) * - `am start` on the resolved launcher activity, NOT `monkey`: monkey was measured * printing its own arguments back and launching nothing while exiting 0. * - DISCOVERY is emulators-only: picking up whatever phone happens to be plugged in * brings USB/authorisation/vendor-skin variance this driver cannot honestly promise * to handle. An EXPLICITLY named serial (option or PI_GUI_ANDROID_SERIAL) is accepted * verbatim, tcp serials included — naming a device is the caller's informed choice, * and it is how redroid containers and benchmark rigs attach. * - Screenshots are downscaled to SHOT_MAX_LONG_SIDE before the model sees them, and * the model's point coordinates are mapped back to device pixels (see image.ts for * why letting the vision API resize silently breaks every tap). * * Input goes through `adb shell input` (tap/swipe/text/keyevent); screenshots through * `adb exec-out screencap -p`. Every call is a fresh adb invocation — there is no * connection to hold, so close() has nothing to tear down and the emulator (the user's * device) is never ours to stop. */ import { GuiDriver } from "../driver.ts"; import type { ActResult, Snapshot, Strategy, TargetInfo, VerbSpec } from "../types.ts"; import { type ExecFn } from "./adb.ts"; export interface AndroidDriverOptions { /** Emulator serial; default PI_GUI_ANDROID_SERIAL, then the first running emulator. */ serial?: string; /** Test seams: a canned exec and a fixed adb path make the driver fully hermetic. */ exec?: ExecFn; adbPath?: string; } export declare class AndroidDriver extends GuiDriver { readonly platform = "android"; readonly strategies: Strategy[]; protected readonly screenshotMimeType: string; private readonly exec; private readonly wantSerial; private adbPath; private serial; private screen; /** Device pixels per pixel of the LAST screenshot the model saw (1 = unscaled). */ private shotToDevice; /** Tap targets for the latest observation's refs, in device pixels. */ private rects; private devicesCache; private avdNames; constructor(opts?: AndroidDriverOptions); private adb; private devices; /** Resolve the target device on first use. An explicitly named serial is accepted * verbatim (the caller's informed choice — tcp devices and phones included); * DISCOVERY picks emulators only, never whatever phone happens to be plugged in. */ private ensure; private shell; private screenSize; /** * `uiautomator dump` — the device's own view of its UI. It writes to a file and prints * only a confirmation, so the file is read back. `2>&1` rides as a bare argument on * purpose: adb joins argv into one line that the DEVICE's shell re-splits, so the * redirect applies there — the only stream the failure (`Killed`) is written to. */ private uiHierarchy; extraVerbs(_strategy: Strategy): VerbSpec[]; promptFragment(strategy: Strategy): string; protected snapshot(): Promise; screenshot(): Promise; readText(): Promise; listTargets(): Promise; /** Friendly device name (`ro.boot.qemu.avd_name`), best-effort and cached — targets * are listed on every observe and getprop is a device round trip. */ private avdName; focusTarget(id: string): Promise; /** Rebind to the device an eval harness prepared (its PI_GUI_ANDROID_SERIAL param). * Wrong serials fail honestly on the next adb call — there is nothing to verify here * that the first primitive would not verify better. */ retargetEnv(params: Record): Promise; perform(kind: string, raw: Record): Promise; close(): Promise; private refCenter; private tap; /** One screen-worth of scroll: a swipe spanning 0.9 of the viewport height, centered. * `to: top/bottom` degrades honestly — Android has no jump, only more scrolling. */ private scroll; /** `am start` on the resolved launcher activity, NOT `monkey` — monkey was measured * printing its arguments back and launching nothing while exiting 0. */ private launchApp; }