Keep every USB-plugged Android phone connected over Wi-Fi adb too (idempotent: never re-runs tcpip on a connected phone)

Keep your Android phone connected without the cable

When you test apps on an Android phone, your computer normally talks to the phone through a USB cable. The phone can also talk to the computer over Wi-Fi. Then you can unplug it, walk around with it, or charge it somewhere else, and the computer still reaches it.

Setting that up by hand takes a few steps every time, and it stops working whenever the phone restarts. This script does it for you.

What it does

When you run it, the script:

  1. Finds every phone that's plugged into your computer by cable.
  2. Looks up each phone's Wi-Fi address. If a phone isn't on Wi-Fi, it tells you to turn Wi-Fi on.
  3. Leaves a phone alone if it's already connected over Wi-Fi. That way it never interrupts something that's running on the phone.
  4. Connects the others over Wi-Fi. If a phone's Wi-Fi address changed since last time, it forgets the old one.

When it's done, you can unplug the cable and keep working.

Before you start

  • A computer with a Terminal, such as a Mac, or Linux.
  • Google's free Android tools for your computer, called "platform-tools", installed. On a Mac with Homebrew: brew install android-platform-tools
  • On the phone: Developer options turned on, and USB debugging allowed in them. The first time you plug in, the phone asks whether to trust this computer: say yes.
  • The phone and the computer on the same Wi-Fi network.

How to use it

  1. Download adb-wireless.sh from this page, and put it in a folder you can find, for example your home folder.
  2. Open the Terminal and go to that folder.
  3. Allow the file to run. You only need to do this once:
    chmod +x adb-wireless.sh
    
  4. Plug in your phone with the cable, and run:
    ./adb-wireless.sh
    
    It tells you, phone by phone, what it did, for example: adb-wireless: Pixel 8 is on Wi-Fi adb at 192.168.1.20:5555.
  5. Unplug the cable. The phone stays connected.

Two other ways to run it

  • Just see what it would do, without changing anything:
    ./adb-wireless.sh --dry-run
    
  • Keep checking every 30 seconds, so a phone that restarts is connected again the next time you plug it in:
    ./adb-wireless.sh --watch
    
    Press Ctrl and C together to stop it.

If something doesn't work

  • "No phone plugged in by USB": check the cable, and that the phone said yes to trusting the computer.
  • "Has no Wi-Fi address": turn on the phone's Wi-Fi.
  • "Couldn't connect": the phone and the computer are probably on different Wi-Fi networks.
  • After the phone restarts, plug it in and run the script again.
#!/usr/bin/env bash
# Local only (listed in .git/info/exclude, never committed): makes sure every phone plugged in by USB is also
# connected over Wi-Fi adb, on port 5555.
#
# scripts/local/adb-wireless.sh # once
# scripts/local/adb-wireless.sh --dry-run # only say what it would do
# scripts/local/adb-wireless.sh --watch # again every 30 s (after a reboot or a new IP), until Ctrl-C
#
# A phone already connected over Wi-Fi is left alone: `adb tcpip` restarts the phone's adbd and would cut a running
# Maestro or Gradle session.
set -uo pipefail
PORT=5555
dry_run=false
watch=false
for arg in "$@"; do
case "$arg" in
--dry-run) dry_run=true ;;
--watch) watch=true ;;
*) echo "usage: $0 [--dry-run] [--watch]" >&2; exit 2 ;;
esac
done
run() { if $dry_run; then echo " would run: $*"; else "$@"; fi; }
# The phone's serial number behind an adb serial (USB or ip:port).
serialno() { adb -s "$1" shell getprop ro.serialno 2>/dev/null | tr -d '\r'; }
ensure_once() {
local usb_serials
usb_serials="$(adb devices -l | awk '$2 == "device" && / usb:/ {print $1}')"
if [ -z "$usb_serials" ]; then
echo "adb-wireless: no phone plugged in by USB"
return 0
fi
# The Wi-Fi connections adb has now, with the phone behind each.
local tcp_serials
tcp_serials="$(adb devices | awk '$2 == "device" && $1 ~ /:[0-9]+$/ {print $1}')"
for usb in $usb_serials; do
local model ip target sn
model="$(adb -s "$usb" shell getprop ro.product.model 2>/dev/null | tr -d '\r')"
sn="$(serialno "$usb")"
ip="$(adb -s "$usb" shell ip -f inet addr show wlan0 2>/dev/null | awk '/inet /{print $2}' | cut -d/ -f1)"
if [ -z "$ip" ]; then
echo "adb-wireless: $model ($usb) has no Wi-Fi address: turn its Wi-Fi on"
continue
fi
target="$ip:$PORT"
local connected=false
for tcp in $tcp_serials; do
[ "$(serialno "$tcp")" = "$sn" ] || continue
if [ "$tcp" = "$target" ]; then
connected=true
else
echo "adb-wireless: $model's old address $tcp is stale (now $target), disconnecting it"
run adb disconnect "$tcp" >/dev/null
fi
done
if $connected; then
echo "adb-wireless: $model is on Wi-Fi adb at $target"
continue
fi
echo "adb-wireless: $model ($usb): switching to TCP mode and connecting to $target"
run adb -s "$usb" tcpip "$PORT" >/dev/null
$dry_run || sleep 2
run adb -s "$usb" wait-for-device
local attempt
for attempt in 1 2 3 4 5; do
$dry_run && { run adb connect "$target"; break; }
adb connect "$target" >/dev/null 2>&1
[ "$(adb -s "$target" get-state 2>/dev/null)" = device ] && break
sleep 1
done
if $dry_run || [ "$(adb -s "$target" get-state 2>/dev/null)" = device ]; then
$dry_run || echo "adb-wireless: $model is on Wi-Fi adb at $target"
else
echo "adb-wireless: couldn't connect to $target: is the Mac on the same Wi-Fi as the phone?" >&2
fi
done
}
if $watch; then
while true; do
ensure_once
sleep 30
done
else
ensure_once
fi
参与讨论