Operating an Android device headless from a laptop over adb: reaching its services, restarting a killed app, and driving the UI by script.
adb devices -l # lists attached devices
USB debugging must be on in developer options. After a fresh boot or adb kill-server, the first adb command restarts the daemon.
sshd and Caddy only listen on the device. Two paths:
# Wi-Fi, normal path ssh -p 8022 192.168.1.82 # USB only: adb forward, no network needed adb forward tcp:18022 tcp:8022 adb forward tcp:18080 tcp:8080 ssh -p 18022 localhost curl http://localhost:18080/
Gotchas:
nc -z through an adb forward gives a false positive: adb accepts the local connection before knowing the device port is dead. Verify with a real probe: curl for HTTP, the SSH-2.0-OpenSSH banner for sshd.ss and netstat are not reliably available. Check listeners on-device via /proc/net/tcp hex ports: adb shell “grep -E ':(1F56|1F90) ' /proc/net/tcp /proc/net/tcp6”.
Swiping Termux out of recents, or a reboot without a subsequent unlock, kills the whole Termux stack. From a shell you cannot broadcast BOOT_COMPLETED (protected intent) or start the Termux service (needs allow-external-apps=true). What works: launch Termux and type the boot command into it.
adb shell "input keyevent KEYCODE_WAKEUP; wm dismiss-keyguard" adb shell "input swipe 200 600 200 100" # dismiss swipe lockscreen adb shell "monkey -p com.termux -c android.intent.category.LAUNCHER 1" sleep 3 adb shell "dumpsys window | grep mCurrentFocus" # verify Termux has focus adb shell "input text 'sh%s~/.termux/boot/start-server.sh'; input keyevent 66"
input text needs %s for spaces. Keyevent 66 is ENTER, 4 is BACK.ps -A | grep -E 'sshd|caddy.adb shell “dumpsys deviceidle whitelist +com.termux”.adb shell "uiautomator dump /data/local/tmp/ui.xml; cat /data/local/tmp/ui.xml" # parse bounds="[l,t][r,b]", tap the center adb shell "input tap X Y"
Quirks:
input tap works on buttons but not RecyclerView grid items in the DocumentsUI file picker. Navigate those with KEYCODE_TAB (walks focusable nodes) and KEYCODE_DPAD_* once the grid has focus; KEYCODE_DPAD_CENTER opens.dumpsys input_method | grep mInputShown and dismiss it with one BACK.dumpsys power | grep mWakefulness before injecting input: the screen sleeps quickly.input swipe x y x+1 y+1 50 is a more reliable tap; 800ms is a long-press.monkey -p <pkg> -c android.intent.category.LAUNCHER 1 launches any app.adb shell "ps -A -o PID,USER,RSS,NAME | sort -k3 -rn | head -40" # toybox ps, no aux adb shell "dumpsys activity lru" # LRU states: fg/vis keep, cch killable, pers respawns adb shell "am force-stop <pkg>" # kill an app and its process tree adb shell "grep MemAvailable /proc/meminfo"