MobDev.Discovery.Android (mob_dev v0.5.5)

Copy Markdown View Source

Discovers Android devices and emulators via adb.

Summary

Functions

Check if developer mode is enabled on the device. Returns :enabled | :disabled | :unknown.

Returns the Mob node-name suffix for the device reachable via the given adb identifier. The suffix is derived from the device's hardware serial (ro.serialno), which is stable across USB and WiFi-adb identifiers for the same physical phone — so a deploy that targets ZY22K6BSJM (USB) and a bench that targets 10.0.0.17:5555 (WiFi) both end up using the same node name.

Returns a list of %Device{} for all adb-visible Android devices.

Sanitizes a string into a Mob node-name suffix. Pure — no adb calls.

Parses the raw output of adb devices -l into a list of %Device{}. Does not perform enrichment (no adb calls for name/version). Exposed for testing.

Restarts the app on the device, optionally passing a dist_port intent extra.

Functions

developer_mode(serial)

@spec developer_mode(String.t()) :: :enabled | :disabled | :unknown

Check if developer mode is enabled on the device. Returns :enabled | :disabled | :unknown.

device_node_suffix(adb_id)

@spec device_node_suffix(String.t()) :: String.t()

Returns the Mob node-name suffix for the device reachable via the given adb identifier. The suffix is derived from the device's hardware serial (ro.serialno), which is stable across USB and WiFi-adb identifiers for the same physical phone — so a deploy that targets ZY22K6BSJM (USB) and a bench that targets 10.0.0.17:5555 (WiFi) both end up using the same node name.

Falls back to sanitizing the adb identifier itself when getprop fails (e.g. unrooted device, missing executable, dead transport). The fallback is the legacy behaviour, kept so a bench against a pre-suffix-aware deploy still converges on some deterministic name.

Single adb shell call (~100–300 ms). Suitable for once-per-launch use by the deployer and bench.

emulator_adb_id?(adb_id)

@spec emulator_adb_id?(String.t()) :: boolean()

emulator_serial?(serial)

@spec emulator_serial?(String.t()) :: boolean()

list_devices()

@spec list_devices() :: [MobDev.Device.t()]

Returns a list of %Device{} for all adb-visible Android devices.

node_suffix_for(serial)

@spec node_suffix_for(String.t()) :: String.t()

Sanitizes a string into a Mob node-name suffix. Pure — no adb calls.

node_suffix_for("ZY22CRLMWK")         "zy22crlmwk"
node_suffix_for("10.0.0.82:5555")     "10_0_0_82"
node_suffix_for("emulator-5554")      "emulator_5554"

Used as the final transformation step by device_node_suffix/1 (which asks the device for a stable hardware serial and runs it through here). Tests use it directly to verify the sanitization rules.

parse_devices_output(output)

@spec parse_devices_output(String.t()) :: [MobDev.Device.t()]

Parses the raw output of adb devices -l into a list of %Device{}. Does not perform enrichment (no adb calls for name/version). Exposed for testing.

restart_app(serial, package, activity, opts \\ [])

@spec restart_app(String.t(), String.t(), String.t(), keyword()) ::
  {:ok, String.t()} | {:error, String.t()}

Restarts the app on the device, optionally passing a dist_port intent extra.

Runs chcon before am start to heal any SELinux MCS category mismatch on OTP files. This mismatch happens when the APK is reinstalled and Android assigns a new MCS category to the package — files pushed via adb push retain the old label and the BEAM can't access them.

The label is copied from the app's cache/ directory, not files/. On Android 15 the files/ directory itself lacks MCS categories (s0 only), whereas cache/ always carries the full s0:cXXX,cYYY label that installd assigns to the package.

The chcon requires root (adb root) — it's silently skipped on non-rooted devices where the OTP files were pushed with the correct label to begin with.