1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 03:38:52 +03:00
This commit is contained in:
Anton Afanasyeu
2026-06-11 18:37:48 +02:00
parent 0431bafd74
commit 9d9a6d66d9
87 changed files with 4443 additions and 1190 deletions

View File

@@ -1,12 +1,19 @@
#!/usr/bin/env bash
# Wireless adb discovery + reconnect helpers (sourced by rebuild.sh).
# Discovers endpoints via: connected devices, adb mdns, HTTP :5039/adb.json, cache, static hints.
# 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:=4}"
: "${ADB_CONNECT_DELAY_SEC:=2}"
: "${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
@@ -25,7 +32,7 @@ adb_hint_key_for_ip() {
adb_ip_for_hint_key() {
local ip=${1}
ip="$(echo "${ip}" | sed -e 's/@_@/./g' -e 's/:.*//' | tr -d '\n')"
ip="$(echo "${ip}" | sed -e 's/_/./g' -e 's/:.*//' | tr -d '\n')"
printf '%s' "${ip}"
}
@@ -36,6 +43,31 @@ adb_hint_for_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
@@ -54,6 +86,12 @@ adb_collect_candidate_ips() {
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
@@ -66,33 +104,75 @@ 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 item:
if ok(item):
print(item)
for item in data.get("commands") or []:
if isinstance(item, str) and item.startswith("adb connect "):
print(item[len("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="$(curl -m 3 -sf "http://${ip}:${DEV_ADB_HTTP_PORT}/adb.json" 2>/dev/null || true)"
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 name service hostport
local line hostport
while IFS= read -r line; do
[[ "$line" == *"_adb._tcp"* ]] || continue
[[ "$line" == List* ]] && continue
[[ "$line" == error:* ]] && continue
[[ "$line" == *"_adb"* ]] || continue
hostport="$(awk '{print $NF}' <<<"$line")"
[[ "$hostport" == *:* ]] || continue
printf '%s\n' "$hostport"
done < <(adb mdns services 2>/dev/null || true)
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() {
@@ -111,45 +191,113 @@ adb_pull_device_json_endpoints() {
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
local endpoint host rank best_rank existing
declare -A seen=()
declare -A host_best=()
declare -A host_rank=()
local -a ordered=()
add_endpoint() {
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
add_endpoint "$endpoint"
done < <(adb_discover_mdns_endpoints)
while IFS= read -r endpoint; do
add_endpoint "$endpoint"
consider_endpoint "$endpoint"
done < <(adb_fetch_http_endpoints)
while IFS= read -r endpoint; do
add_endpoint "$endpoint"
done < <(adb_pull_device_json_endpoints)
consider_endpoint "$endpoint"
done < <(adb_discover_avahi_endpoints)
if [[ -f "$ADB_CONNECT_CACHE" ]]; then
while IFS= read -r endpoint; do
add_endpoint "$endpoint"
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")"
add_endpoint "${ip}:5555"
add_endpoint "${ip}"
[[ -n "${host_best[$ip]:-}" ]] && continue
consider_endpoint "${ip}:5555"
done
printf '%s\n' "${ordered[@]}"
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() {
@@ -162,21 +310,86 @@ adb_poke_refresh_broadcast() {
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
local attempt out hint
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})"
echo " adb connect ${endpoint} ${hint}"
adb disconnect "$endpoint" >/dev/null 2>&1 || true
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
if grep -qiE 'connected|already' <<<"$out"; then
if adb -s "$endpoint" get-state 2>/dev/null | grep -q '^device$'; then
echo " ${endpoint} — ready"
return 0
fi
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"
@@ -191,28 +404,62 @@ adb_update_connect_cache() {
mkdir -p "$(dirname "$ADB_CONNECT_CACHE")"
: >"$ADB_CONNECT_CACHE"
while IFS= read -r serial; do
[[ "$serial" == *:* ]] || continue
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_all() {
echo "==> Reconnecting wireless adb targets"
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
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
if adb_reconnect_endpoint "$endpoint"; then
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
@@ -224,3 +471,27 @@ adb_reconnect_all() {
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 /'
}

View File

@@ -1,6 +1,15 @@
#!/usr/bin/env bash
# Render every docs/*.md to docs/<stem>.pdf (reportlab; see docs/_pdf_build/md_to_pdf.py).
# Render docs/**/*.md to sibling .pdf, then diagram/special PDF builders.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
python3 "$ROOT_DIR/docs/_pdf_build/build_all_docs_pdf.py"
echo "Done: $ROOT_DIR/docs/*.pdf"
PDF_BUILD="$ROOT_DIR/docs/_pdf_build"
python3 "$PDF_BUILD/build_all_docs_pdf.py"
for extra in \
build_git_flow_pdf.py \
build_graphvis_pivot_pdf.py \
build_egress_review_pdf.py; do
if [[ -f "$PDF_BUILD/$extra" ]]; then
python3 "$PDF_BUILD/$extra" || echo "WARN: $extra failed (optional)" >&2
fi
done
echo "Done: PDFs under $ROOT_DIR/docs/ (recursive + diagram builders)"

View File

@@ -32,9 +32,23 @@ DOC_BLURBS: dict[str, str] = {
"USB_HDMI_CAST.md": "Roadmap D/E: HDMI awareness, USB-tether transport.",
"GRAFANA_vs_others_graphvis_pivot.md": "Custom graphs vs Grafana decision memo.",
"20260602_REVERSE_SSH_proposals_summary.md": "On-demand remote access (WG/SSH/file API) proposals.",
"20260607-2FA-email-mobile-auth-flow.md": "Email forwarding + registration/2FA (alpha must-have).",
"20260608_ALPHA_PRIORITIES.md": "**Master priority plan** (0.19.x renumber, owners, alpha path).",
"20260608_BE_SERVICES_and_infra.md": "BE service map, validation status, SFU preview (gray).",
"20260608_STREAM_DUMP_DEBUG.md": "FR 0.1 stream dump spec.",
"20260610_SERVICES.md": "Exposed services catalog — web UI, APIs, curl, validation (2026-06-10).",
"OPEN_TASKS_GRAPH.md": "Open tasks dependency graph (Mermaid).",
"OPUS_SPEEX_VALIDATION.md": "Opus/Speex + UDP stream protection E2E checklist.",
"REMOTE_ACCESS_IMPL.md": "WireGuard v1 impl + deploy.",
"REMOTE_ACCESS_VALIDATION.md": "Linux CLI suite + mobile parity.",
"AV_QUALITY_QA_SESSION.md": "AV enhancement Q&A: sender vs receiver pipelines.",
"specs/20100611_3_url_shortener.md": "URL shortener service — SPEC (`s.f0xx.org`, post-alpha).",
"DRs/20100611_3_url_shortener.md": "URL shortener — design review (auth, slug algo, task graph).",
}
SPEC_DIR = DOCS / "specs"
DR_DIR = DOCS / "DRs"
def github_slug(text: str) -> str:
"""Approximate GitHub/Cursor heading anchor slugs."""
@@ -150,6 +164,8 @@ def build_index_md() -> str:
"",
TOC_START,
"- [All documents](#all-documents)",
"- [Specifications](#specifications)",
"- [Design reviews](#design-reviews)",
"- [By topic](#by-topic)",
"- [Agent bootstrap](#agent-bootstrap)",
TOC_END,
@@ -166,6 +182,43 @@ def build_index_md() -> str:
blurb = DOC_BLURBS.get(name, "")
lines.append(f"| [{name}]({name}) | {blurb} |")
spec_files = sorted(SPEC_DIR.glob("*.md")) if SPEC_DIR.is_dir() else []
dr_files = sorted(DR_DIR.glob("*.md")) if DR_DIR.is_dir() else []
lines += [
"",
"---",
"",
"## Specifications",
"",
"| Document | Summary |",
"|----------|---------|",
]
if spec_files:
for p in spec_files:
rel = f"specs/{p.name}"
blurb = DOC_BLURBS.get(rel, "")
lines.append(f"| [{p.name}]({rel}) | {blurb} |")
else:
lines.append("| _(none yet)_ | |")
lines += [
"",
"---",
"",
"## Design reviews",
"",
"| Document | Summary |",
"|----------|---------|",
]
if dr_files:
for p in dr_files:
rel = f"DRs/{p.name}"
blurb = DOC_BLURBS.get(rel, "")
lines.append(f"| [{p.name}]({rel}) | {blurb} |")
else:
lines.append("| _(none yet)_ | |")
lines += [
"",
"---",
@@ -195,6 +248,8 @@ def build_index_md() -> str:
f"- [TICKETS_ROADMAP.md](TICKETS_ROADMAP.md) — tickets + Gitea",
f"- [GRAFANA_vs_others_graphvis_pivot.md](GRAFANA_vs_others_graphvis_pivot.md) — graphs pivot",
f"- [20260602_REVERSE_SSH_proposals_summary.md](20260602_REVERSE_SSH_proposals_summary.md) — remote access ([§Rev. 3 Q&A](20260602_REVERSE_SSH_proposals_summary.md#rev-3--wg-vs-ssh-qa--bastion-deep-dive))",
f"- [specs/20100611_3_url_shortener.md](specs/20100611_3_url_shortener.md) — URL shortener SPEC ([§REST API](specs/20100611_3_url_shortener.md#6-rest-api))",
f"- [DRs/20100611_3_url_shortener.md](DRs/20100611_3_url_shortener.md) — URL shortener DR ([§Resolved open questions](DRs/20100611_3_url_shortener.md#2-resolved-open-questions))",
"",
"### Product and codecs",
"",
@@ -235,10 +290,15 @@ def main() -> None:
print(f"Wrote {index_path}")
changed = []
for path in sorted(DOCS.glob("*.md")):
toc_paths: list[Path] = sorted(DOCS.glob("*.md"))
if SPEC_DIR.is_dir():
toc_paths.extend(sorted(SPEC_DIR.glob("*.md")))
if DR_DIR.is_dir():
toc_paths.extend(sorted(DR_DIR.glob("*.md")))
for path in toc_paths:
if process_file(path):
changed.append(path.name)
print(f"Updated TOC: {path.name}")
changed.append(str(path.relative_to(DOCS)))
print(f"Updated TOC: {path.relative_to(DOCS)}")
print(f"Done. {len(changed)} file(s) updated.")

18
scripts/grant-dev-adb-perms.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# One-time grants while device is connected (usually USB). Debug package only.
set -euo pipefail
PACKAGE="${1:-com.foxx.androidcast}"
if ! adb get-state >/dev/null 2>&1; then
echo "ERROR: no adb device in 'device' state" >&2
exit 1
fi
echo "==> Granting dev adb permissions to ${PACKAGE}"
adb shell pm grant "${PACKAGE}" android.permission.WRITE_SECURE_SETTINGS
adb shell pm grant "${PACKAGE}" android.permission.DUMP
adb shell am broadcast \
-a com.foxx.androidcast.dev.REFRESH_ADB_WIFI \
-n "${PACKAGE}/.dev.DevAdbWifiReceiver"
echo "OK. Re-check: curl -s http://DEVICE:5039/adb.json | grep secure_settings_granted"