Documentation
¶
Overview ¶
Package avd is the Android counterpart to internal/simctl: a thin, read-only discovery wrapper over the Android SDK's `emulator` and `adb` for finding the AVDs (Android Virtual Devices) installed on this machine. Like simctl it does not build or install anything; the one mutation it exposes (Boot) is opt-in, best-effort, and used only for `claim --boot`.
Android has no single JSON command like `xcrun simctl list -j`. Discovery is a plaintext join across three sources, every quirk verified against a real SDK:
- `emulator -list-avds` -> installed AVD names (the allocatable pool)
- `adb devices -l` -> running serials (emulator-<port>) + state
- `adb -s <serial> emu avd name` -> maps a running serial back to its AVD name
The AVD name is the stable identity (device.Device.ID); the emulator-<port> serial is a transient console-port handle and is deliberately not the identity. One AVD name == one allocatable slot (see docs/ANDROID.md).
Index ¶
- Constants
- type Client
- func (c *Client) ADBHealth(ctx context.Context) error
- func (c *Client) ADBPath() string
- func (c *Client) Boot(_ context.Context, id string) error
- func (c *Client) Class() device.Class
- func (c *Client) Configured() bool
- func (c *Client) EmulatorPath() string
- func (c *Client) ListAvailable(ctx context.Context) ([]device.Device, error)
- func (c *Client) SDKRoot() string
- func (c *Client) Shutdown(ctx context.Context, avdName string, confirmKill func() bool) error
- type Runner
Constants ¶
const DefaultTimeout = 15 * time.Second
DefaultTimeout bounds a whole discovery pass (list-avds + adb calls + one console round-trip per running emulator) so a wedged adb daemon can't hang a claim. It is independent of simctl's timeout.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to the Android SDK. The zero value is NOT ready — use New.
func New ¶
func New() *Client
New returns a Client backed by the real emulator/adb binaries, resolving the SDK and AVD home from the environment. If no SDK is found, sdkRoot is "" and ListAvailable cleanly returns no devices (an iOS-only machine).
func NewWithRunner ¶
NewWithRunner returns a Client backed by a custom Runner (tests). sdkRoot must be non-empty for discovery to proceed; avdHome may be set by the caller.
func (*Client) ADBHealth ¶
ADBHealth probes adb (start-server + devices) and returns the error unswallowed, so `doctor` can distinguish a wedged/errored adb from a healthy adb that simply has no emulators running. A no-op (nil) when no SDK is present.
func (*Client) Boot ¶
Boot is a best-effort, detached launch of the emulator for `claim --boot`. It returns as soon as the process is spawned — it does NOT wait for Android to finish booting (that's the consumer's concern). An Android cold boot takes tens of seconds; never put that on the claim path. Allocation never depends on this succeeding.
func (*Client) Configured ¶
Configured reports whether an Android SDK was found (used by `doctor`).
func (*Client) EmulatorPath ¶
EmulatorPath / ADBPath are the absolute binary paths (for `doctor` checks).
func (*Client) ListAvailable ¶
ListAvailable returns every installed AVD as a class-neutral device.Device, joining the installed list with adb's running set so each carries its current state. An unconfigured machine (no SDK) returns no devices and no error.
func (*Client) Shutdown ¶
Shutdown best-effort terminates the running emulator backing an AVD name, to free its (substantial) RAM when a claim is released or reclaimed. It re-checks that the running serial still maps to this exact AVD name before killing — console ports are reused, so a stale serial could otherwise point at a different emulator. A no-op when no SDK is configured or the AVD isn't running.
confirmKill, if non-nil, is consulted at the LAST moment — after the running serial is resolved, immediately before the kill — and the kill is skipped if it returns false. The broker uses this to re-check, under its lock, that no concurrent claim has re-acquired the device in the gap since the claim was released/reclaimed (the teardown-vs-reclaim race), shrinking that window to just the kill syscall.