#!/usr/bin/env bash # Wireless adb discovery + reconnect helpers (sourced by rebuild.sh). # Priority: app HTTP :5039/adb.json → avahi → cache → mdns → :5555 hints. # Only IPv4:PORT is used for adb connect (no mDNS service names). : "${ADB_CONNECT_RETRIES:=3}" : "${ADB_CONNECT_DELAY_SEC:=1}" : "${ADB_TCP_PROBE:=yes}" : "${ADB_TCP_PROBE_SEC:=1}" : "${DEV_ADB_HTTP_PORT:=5039}" : "${ADB_CONNECT_CACHE:=$ROOT/.adb_connect_cache}" : "${ADB_HTTP_CACHE_DIR:=$ROOT/.adb_http_cache}" : "${ADB_RECONNECT_POKE_DELAY_SEC:=2}" : "${ADB_MDNS_AUTO_CONNECT:=adb-tls-connect,adb}" : "${ADB_KILL_SERVER:=no}" : "${ADB_DISCONNECT_BEFORE_CONNECT:=no}" declare -gA CONNECTIONS_LIST_HINTS adb_reconnect_init_hints() { CONNECTIONS_LIST_HINTS["192_168_33_153"]="[A13] Android Projector" CONNECTIONS_LIST_HINTS["192_168_33_39"]="[A16] Doogee Tab G6 Max / P8_EEA" CONNECTIONS_LIST_HINTS["192_168_33_106"]="[A11] BlackView BL6000Pro / BL6000Pro_EEA" CONNECTIONS_LIST_HINTS["192_168_33_173"]="[A11] BlackView BL6000Pro / BL6000Pro" } adb_hint_key_for_ip() { local ip=${1} ip="$(echo "${ip}" | sed -e 's@\.@_@g' -e 's@:.*@@' | tr -d '\n')" printf '%s' "${ip}" } adb_ip_for_hint_key() { local ip=${1} ip="$(echo "${ip}" | sed -e 's/_/./g' -e 's/:.*//' | tr -d '\n')" printf '%s' "${ip}" } adb_hint_for_endpoint() { local endpoint=${1} local key key="$(adb_hint_key_for_ip "${endpoint}")" printf '%s' "${CONNECTIONS_LIST_HINTS[${key}]:-}" } adb_endpoint_host() { local endpoint=${1} if [[ "$endpoint" =~ ^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+): ]]; then printf '%s' "${BASH_REMATCH[1]}" fi } adb_is_connectable_ip_port() { local endpoint=${1} [[ "$endpoint" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+$ ]] } adb_tcp_port_open() { local endpoint=${1} local host port host="$(adb_endpoint_host "$endpoint")" port="${endpoint##*:}" [[ -n "$host" && "$port" =~ ^[0-9]+$ ]] || return 1 if command -v nc >/dev/null 2>&1; then nc -z -w "${ADB_TCP_PROBE_SEC}" "$host" "$port" >/dev/null 2>&1 return $? fi timeout "${ADB_TCP_PROBE_SEC}" bash -c "exec 3<>/dev/tcp/${host}/${port}" 2>/dev/null } adb_collect_candidate_ips() { local ip endpoint key if [[ -f "$ADB_CONNECT_CACHE" ]]; then while IFS= read -r endpoint; do [[ -z "$endpoint" ]] && continue ip="${endpoint%%:*}" [[ -n "$ip" ]] && printf '%s\n' "$ip" done < "$ADB_CONNECT_CACHE" fi for key in "${!CONNECTIONS_LIST_HINTS[@]}"; do adb_ip_for_hint_key "$key" done adb devices -l 2>/dev/null | awk '/^[^ ]+ +device/ { print $1 }' | while read -r serial; do [[ "$serial" == *:* ]] || continue printf '%s\n' "${serial%%:*}" done } adb_json_valid() { local json=${1} [[ -n "$json" ]] || return 1 printf '%s' "$json" | python3 -c 'import json,sys; json.loads(sys.stdin.read())' 2>/dev/null } adb_json_connect_lines() { local json=${1} [[ -n "$json" ]] || return 0 printf '%s' "$json" | python3 -c ' import json, sys raw = sys.stdin.read() if not raw.strip(): raise SystemExit(0) try: data = json.loads(raw) except json.JSONDecodeError: raise SystemExit(0) tls = int(data.get("tls_port") or 0) if tls > 0: for ip in data.get("ips") or []: if ip: print(f"{ip}:{tls}") import re IP_PORT = re.compile(r"^\d{1,3}(?:\.\d{1,3}){3}:\d+$") def ok(ep): return bool(ep and IP_PORT.match(ep)) for item in data.get("connect") or []: if ok(item): print(item) for item in data.get("commands") or []: if isinstance(item, str) and item.startswith("adb connect "): ep = item[len("adb connect "):] if ok(ep): print(ep) ' 2>/dev/null || true } adb_curl_adb_json() { local ip=${1} local json cached="${ADB_HTTP_CACHE_DIR}/${ip}.json" json="$(curl -m 5 -sS "http://${ip}:${DEV_ADB_HTTP_PORT}/adb.json" 2>/dev/null || true)" if adb_json_valid "$json"; then mkdir -p "$ADB_HTTP_CACHE_DIR" printf '%s' "$json" >"$cached" printf '%s' "$json" return 0 fi if [[ -f "$cached" ]] && adb_json_valid "$(cat "$cached")"; then cat "$cached" return 0 fi return 1 } adb_fetch_http_endpoints() { local ip json for ip in $(adb_collect_candidate_ips | sort -u); do [[ -z "$ip" ]] && continue json="$(adb_curl_adb_json "$ip" 2>/dev/null || true)" [[ -z "$json" ]] && continue adb_json_connect_lines "$json" done } adb_discover_mdns_endpoints() { local line hostport while IFS= read -r line; do [[ "$line" == List* ]] && continue [[ "$line" == error:* ]] && continue [[ "$line" == *"_adb"* ]] || continue hostport="$(awk '{print $NF}' <<<"$line")" if adb_is_connectable_ip_port "$hostport"; then printf '%s\n' "$hostport" fi done < <(adb mdns services 2>&1 || true) } adb_discover_avahi_endpoints() { command -v avahi-browse >/dev/null 2>&1 || return 0 local svc for svc in _adb-tls-connect._tcp _adb._tcp; do avahi-browse -rpt "$svc" 2>/dev/null | awk -F';' ' $1 == "=" && $3 == "IPv4" && $8 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ && $9 ~ /^[0-9]+$/ { print $8 ":" $9 }' done } adb_pull_device_json_endpoints() { local serial json tmp while IFS= read -r serial; do [[ -z "$serial" ]] && continue tmp="$(mktemp)" if adb -s "$serial" exec-out run-as com.foxx.androidcast cat files/dev/adb_connect.json \ >"$tmp" 2>/dev/null; then json="$(cat "$tmp")" rm -f "$tmp" adb_json_connect_lines "$json" else rm -f "$tmp" fi done < <(adb devices 2>/dev/null | awk 'NR > 1 && $2 == "device" { print $1 }') } adb_endpoint_rank() { local e=${1} if [[ "$e" == *":5555" ]]; then printf '9' else printf '2' fi } # One endpoint per IPv4 host; keep best rank (HTTP/avahi TLS beats :5555). adb_discover_endpoints() { local endpoint host rank best_rank existing declare -A seen=() declare -A host_best=() declare -A host_rank=() local -a ordered=() consider_endpoint() { local e=${1} [[ -z "$e" ]] && return 0 if [[ "$e" == *_* && "$e" != *":"* ]]; then e="$(adb_ip_for_hint_key "$e")" fi [[ -z "$e" ]] && return 0 if ! adb_is_connectable_ip_port "$e"; then return 0 fi host="$(adb_endpoint_host "$e")" rank="$(adb_endpoint_rank "$e")" if [[ -n "$host" ]]; then existing="${host_best[$host]:-}" if [[ -n "$existing" ]]; then best_rank="${host_rank[$host]}" if [[ "$rank" -ge "$best_rank" ]]; then return 0 fi seen["$existing"]=0 for i in "${!ordered[@]}"; do if [[ "${ordered[$i]}" == "$existing" ]]; then unset 'ordered[i]' fi done ordered=("${ordered[@]}") fi host_best["$host"]="$e" host_rank["$host"]="$rank" fi [[ -n "${seen[$e]:-}" ]] && return 0 seen["$e"]=1 ordered+=("$e") } # HTTP first — matches curl the user runs manually. while IFS= read -r endpoint; do consider_endpoint "$endpoint" done < <(adb_fetch_http_endpoints) while IFS= read -r endpoint; do consider_endpoint "$endpoint" done < <(adb_discover_avahi_endpoints) if [[ -f "$ADB_CONNECT_CACHE" ]]; then while IFS= read -r endpoint; do consider_endpoint "$endpoint" done < "$ADB_CONNECT_CACHE" fi while IFS= read -r endpoint; do consider_endpoint "$endpoint" done < <(adb_pull_device_json_endpoints) while IFS= read -r endpoint; do consider_endpoint "$endpoint" done < <(adb_discover_mdns_endpoints) local key ip for key in "${!CONNECTIONS_LIST_HINTS[@]}"; do ip="$(adb_ip_for_hint_key "$key")" [[ -n "${host_best[$ip]:-}" ]] && continue consider_endpoint "${ip}:5555" done adb_filter_endpoints_by_tcp "${ordered[@]}" } # Drop closed ports (fast nc/bash probe). If none open, return candidates anyway. adb_filter_endpoints_by_tcp() { local -a all=("$@") local -a open=() local e if [[ "${ADB_TCP_PROBE}" =~ ^[nN] ]]; then printf '%s\n' "${all[@]}" return 0 fi for e in "${all[@]}"; do [[ -z "$e" ]] && continue if adb_tcp_port_open "$e"; then open+=("$e") else echo " probe ${e} — closed (skip)" >&2 fi done if [[ ${#open[@]} -gt 0 ]]; then printf '%s\n' "${open[@]}" else printf '%s\n' "${all[@]}" fi } adb_poke_refresh_broadcast() { local serial while IFS= read -r serial; do [[ -z "$serial" ]] && continue adb -s "$serial" shell am broadcast \ -a com.foxx.androidcast.dev.REFRESH_ADB_WIFI \ -n com.foxx.androidcast/.dev.DevAdbWifiReceiver >/dev/null 2>&1 || true done < <(adb devices 2>/dev/null | awk 'NR > 1 && $2 == "device" { print $1 }') } # Wireless TLS adb often shows as adb-xxx._adb-tls-connect._tcp, not IP:port. adb_resolve_device_serial() { local endpoint=${1} local serial state host count=0 last="" if adb -s "$endpoint" get-state 2>/dev/null | grep -q '^device$'; then printf '%s' "$endpoint" return 0 fi host="$(adb_endpoint_host "$endpoint")" while IFS= read -r serial state; do [[ "$state" != "device" ]] && continue count=$((count + 1)) last="$serial" if [[ "$serial" == "$endpoint" ]]; then printf '%s' "$serial" return 0 fi if [[ -n "$host" && "$serial" == *"$host"* ]]; then printf '%s' "$serial" return 0 fi done < <(adb devices 2>/dev/null | awk 'NR > 1 && NF >= 2 { print $1, $2 }') if [[ "$count" -eq 1 && -n "$last" ]]; then printf '%s' "$last" return 0 fi return 1 } adb_parse_connect_serial() { local out=${1} if [[ "$out" =~ [Cc]onnected\ to\ ([^[:space:]]+) ]]; then printf '%s' "${BASH_REMATCH[1]}" return 0 fi return 1 } adb_host_has_device() { local host=${1} local serial serial="$(adb_resolve_device_serial "${host}:1" 2>/dev/null || true)" [[ -n "$serial" ]] } adb_reconnect_endpoint() { local endpoint="$1" [[ -z "$endpoint" ]] && return 1 if ! adb_is_connectable_ip_port "$endpoint"; then echo " skip ${endpoint} — not IPv4:PORT" >&2 return 1 fi local attempt out hint resolved hint="$(adb_hint_for_endpoint "${endpoint}")" [[ -n "${hint}" ]] && hint="(${hint})" if [[ "${ADB_TCP_PROBE}" =~ [yY] ]] && ! adb_tcp_port_open "$endpoint"; then echo " skip ${endpoint} — TCP closed ${hint}" >&2 return 1 fi resolved="$(adb_resolve_device_serial "$endpoint" 2>/dev/null || true)" if [[ -n "$resolved" ]]; then echo " ${endpoint} already connected as ${resolved} ${hint}" >&2 printf '%s' "$resolved" return 0 fi echo " adb connect ${endpoint} ${hint}" >&2 if [[ "${ADB_DISCONNECT_BEFORE_CONNECT}" =~ [yY] ]]; then adb disconnect "$endpoint" >/dev/null 2>&1 || true fi for (( attempt = 1; attempt <= ADB_CONNECT_RETRIES; attempt++ )); do out="$(adb connect "$endpoint" 2>&1)" || true sleep 1 resolved="$(adb_parse_connect_serial "$out" 2>/dev/null || true)" if [[ -z "$resolved" ]]; then resolved="$(adb_resolve_device_serial "$endpoint" 2>/dev/null || true)" fi if [[ -n "$resolved" ]]; then echo " ${resolved} — ready (${out})" >&2 printf '%s' "$resolved" return 0 fi if [[ "$attempt" -lt "$ADB_CONNECT_RETRIES" ]]; then sleep "$ADB_CONNECT_DELAY_SEC" fi done echo " ${endpoint} — not reachable (${out})" >&2 return 1 } adb_update_connect_cache() { local serial mkdir -p "$(dirname "$ADB_CONNECT_CACHE")" : >"$ADB_CONNECT_CACHE" while IFS= read -r serial; do adb_is_connectable_ip_port "$serial" || continue echo "$serial" >>"$ADB_CONNECT_CACHE" done < <(adb devices 2>/dev/null | awk 'NR > 1 && $2 == "device" { print $1 }') } adb_reconnect_prepare() { export ADB_MDNS_AUTO_CONNECT adb start-server >/dev/null 2>&1 || true if [[ "${ADB_KILL_SERVER}" =~ [yY] ]]; then adb kill-server >/dev/null 2>&1 || true adb start-server >/dev/null 2>&1 || true fi adb reconnect offline >/dev/null 2>&1 || true } adb_reconnect_all() { local label=${1:-post-build} echo "==> Reconnecting wireless adb targets (${label})" adb_reconnect_prepare adb_poke_refresh_broadcast if [[ "${ADB_RECONNECT_POKE_DELAY_SEC:-0}" -gt 0 ]]; then sleep "$ADB_RECONNECT_POKE_DELAY_SEC" fi local endpoint connected=0 host resolved declare -A connected_hosts=() while IFS= read -r serial; do host="$(adb_endpoint_host "$serial")" [[ -n "$host" ]] && connected_hosts["$host"]="$serial" done < <(adb devices 2>/dev/null | awk 'NR > 1 && $2 == "device" { print $1 }') while IFS= read -r endpoint; do [[ -z "$endpoint" ]] && continue host="$(adb_endpoint_host "$endpoint")" if [[ -n "$host" && -n "${connected_hosts[$host]:-}" ]]; then echo " skip ${endpoint} — ${host} already on ${connected_hosts[$host]}" continue fi if resolved="$(adb_reconnect_endpoint "$endpoint")"; then connected=$((connected + 1)) host="$(adb_endpoint_host "$resolved")" [[ -z "$host" ]] && host="$(adb_endpoint_host "$endpoint")" [[ -n "$host" ]] && connected_hosts["$host"]="$resolved" fi done < <(adb_discover_endpoints) if [[ "$connected" -eq 0 ]]; then echo " retry: HTTP-only pass" while IFS= read -r endpoint; do [[ -z "$endpoint" ]] && continue if resolved="$(adb_reconnect_endpoint "$endpoint")"; then connected=$((connected + 1)) fi done < <(adb_fetch_http_endpoints) fi adb_update_connect_cache adb devices -l if [[ "$connected" -gt 0 ]]; then return 0 fi return 1 } adb_count_ready_devices() { adb devices 2>/dev/null | awk 'NR > 1 && $2 == "device" { c++ } END { print c+0 }' } adb_prepare_device_for_deploy() { local serial=${1:-} local -a adb_args=() [[ -n "$serial" ]] && adb_args=(-s "$serial") adb "${adb_args[@]}" shell input keyevent KEYCODE_WAKEUP >/dev/null 2>&1 \ || adb "${adb_args[@]}" shell input keyevent 224 >/dev/null 2>&1 \ || true adb "${adb_args[@]}" shell am broadcast \ -a com.foxx.androidcast.dev.PREPARE_FOR_DEPLOY \ -n com.foxx.androidcast/.dev.DebugDeployWakeReceiver >/dev/null 2>&1 || true } adb_http_status() { local ip=${1} local json json="$(adb_curl_adb_json "$ip" 2>/dev/null || true)" if [[ -z "$json" ]]; then echo "${ip}: no response (app not running or port ${DEV_ADB_HTTP_PORT} closed)" return 1 fi echo "${ip}: ok" adb_json_connect_lines "$json" | sed 's/^/ connect /' }