Packages
mob_dev
0.5.3
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/mob.devices.ex
defmodule Mix.Tasks.Mob.Devices do
use Mix.Task
@shortdoc "List all connected Android and iOS devices"
@moduledoc """
Scans for connected Android devices (via adb) and iOS simulators/physical
devices (via xcrun simctl / ideviceinfo) and prints their status.
mix mob.devices
Each device is shown with a short **ID** you can pass to `--device`:
mix mob.deploy --device emulator-5554
mix mob.deploy --native --device 78354490
Gracefully skips platforms whose tools are not installed (adb / xcrun).
## Under the hood
# Android
adb devices -l
# → parses serial numbers, device/emulator state, and manufacturer/model
# iOS (macOS only)
xcrun simctl list devices booted --json
ideviceinfo -k UniqueDeviceID (if libimobiledevice is installed)
"""
alias MobDev.Discovery.{Android, IOS}
alias MobDev.Device
@impl Mix.Task
def run(_args) do
Mix.Task.run("app.config")
android = list_android()
ios = list_ios()
IO.puts("")
print_section("Android", android)
IO.puts("")
print_section("iOS", ios)
all = device_list(android) ++ device_list(ios)
if all != [] do
IO.puts("")
IO.puts("Pass the ID to --device to target a specific device:")
IO.puts(" mix mob.deploy --device #{Device.display_id(hd(all))}")
print_bench_hints(all)
end
IO.puts("")
end
defp print_bench_hints(all) do
physical_ios_with_ip =
Enum.find(all, fn d ->
d.platform == :ios and d.type == :physical and d.host_ip
end)
physical_android =
Enum.find(all, fn d -> d.platform == :android and d.type == :physical end)
if physical_ios_with_ip do
IO.puts("")
IO.puts("For an iOS battery bench, use --wifi-ip with the device IP:")
IO.puts(" mix mob.battery_bench_ios --no-build --wifi-ip #{physical_ios_with_ip.host_ip}")
end
if physical_android do
IO.puts("")
IO.puts("For an Android battery bench, target the device by serial:")
IO.puts(" mix mob.battery_bench_android --no-build --device #{physical_android.serial}")
end
end
# ── Device discovery (returns tagged list or a reason atom) ──────────────────
defp list_android do
case System.find_executable("adb") do
nil -> {:unavailable, "adb not found — install Android platform-tools"}
_ -> {:ok, Android.list_devices()}
end
end
defp list_ios do
cond do
not macos?() ->
{:unavailable, "iOS deployment requires macOS"}
System.find_executable("xcrun") == nil ->
{:unavailable, "xcrun not found — install Xcode command-line tools"}
true ->
{:ok, IOS.list_devices()}
end
end
# ── Output ───────────────────────────────────────────────────────────────────
defp print_section(title, result) do
IO.puts("#{IO.ANSI.cyan()}#{title}#{IO.ANSI.reset()}")
case result do
{:unavailable, reason} ->
IO.puts(" (#{reason})")
{:ok, []} ->
IO.puts(" (none)")
{:ok, devices} ->
print_table(devices)
Enum.each(devices, &print_hints/1)
end
end
defp print_table(devices) do
max_name = devices |> Enum.map(&name_len/1) |> Enum.max()
max_ver = devices |> Enum.map(&ver_len/1) |> Enum.max()
max_type = devices |> Enum.map(&type_len/1) |> Enum.max()
max_id = devices |> Enum.map(&id_len/1) |> Enum.max()
any_ip = Enum.any?(devices, & &1.host_ip)
Enum.each(devices, fn d ->
icon = status_icon(d)
name = pad(d.name || d.serial, max_name)
ver = pad(d.version || "", max_ver)
type = pad(type_label(d), max_type)
id_str = Device.display_id(d)
id_padded = pad(id_str, max_id)
id = IO.ANSI.bright() <> id_padded <> IO.ANSI.reset()
ip_part =
cond do
d.host_ip -> " " <> IO.ANSI.faint() <> d.host_ip <> IO.ANSI.reset()
any_ip -> " " <> IO.ANSI.faint() <> "(no IP)" <> IO.ANSI.reset()
true -> ""
end
IO.puts(" #{icon} #{name} #{ver} #{type} #{id}#{ip_part}")
end)
end
defp name_len(d), do: String.length(d.name || d.serial)
defp ver_len(d), do: String.length(d.version || "")
defp type_len(d), do: String.length(type_label(d))
defp id_len(d), do: String.length(Device.display_id(d))
defp type_label(%{type: :emulator}), do: "emulator"
defp type_label(%{type: :simulator}), do: "simulator"
defp type_label(%{type: :physical}), do: "physical"
defp type_label(_), do: "device"
defp status_icon(%{status: :connected}), do: "✓"
defp status_icon(%{status: :booted}), do: "·"
defp status_icon(%{status: :discovered}), do: "·"
defp status_icon(%{status: :unauthorized}), do: "✗"
defp status_icon(%{status: :error}), do: "!"
defp status_icon(_), do: "·"
defp pad(str, width), do: String.pad_trailing(str, width)
# ── Hints ────────────────────────────────────────────────────────────────────
defp print_hints(%{status: :unauthorized}) do
IO.puts(
" #{IO.ANSI.yellow()}→ Check device for 'Allow USB debugging?' prompt#{IO.ANSI.reset()}"
)
end
defp print_hints(%{platform: :android, serial: serial}) do
case Android.developer_mode(serial) do
:disabled ->
IO.puts(
" #{IO.ANSI.yellow()}→ Enable Developer Mode: Settings → About → tap Build Number 7×#{IO.ANSI.reset()}"
)
_ ->
:ok
end
end
defp print_hints(_), do: :ok
defp device_list({:ok, devices}), do: devices
defp device_list({:unavailable, _}), do: []
defp macos?, do: match?({:unix, :darwin}, :os.type())
end