Files
404-Layers/scripts/device.sh
ncode e65591431c Stop device:launch crying wolf on release builds
A release APK is not DEBUGGABLE, so Capacitor never enables web-contents
debugging and the devtools socket is never opened. page_loaded waited for that
socket regardless, so it reported "renderer denied" three times for a healthy
app, force-stopping and restarting it on each retry. It now branches: debug
builds still wait for the page to parse; release builds check the process
survived with no denial against our package, and say the page load is assumed
rather than claiming a verification that is not possible over adb.

The failure diagnostic was unfiltered and unbounded, so it printed other apps'
denials from minutes earlier. It is now scoped to our package and to the
current launch.

Also fixes keep-awake, which still ran the "usb" form that CLAUDE.md had
already recorded as ineffective on this AC-charging phone, and corrects the
documented value: "true" measures as 15 here, not 7.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:56:59 +03:00

150 lines
5.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Device helpers for driving the app over adb.
#
# TCL's AppBootManagerService sometimes refuses to let the app bind the
# WebView's renderer process:
#
# W/AppBootManager: Result[false]; Target[...SandboxedProcessService0];
# Caller[dev.ncode.sp404layers]; Reason[screen_on & caller care]
# E/cr_ChildProcessConn: Failed to establish the service connection.
#
# With no renderer the page never parses: no crash, no JS error, an empty
# document.title, and DevTools Runtime.evaluate hangs. It is sticky for the life
# of the process, so reopening from Recents keeps the blank window.
#
# Observed trigger is the window just after `adb install -r` replaces the
# package - every failure followed an install, and an immediate relaunch with no
# install has always succeeded. The Reason string reports the current screen
# state ("screen_on" and "screen_off" have both been seen denying), so screen
# state is not the cause. Hence: launch retries the whole cycle.
set -euo pipefail
ADB="${ADB:-$HOME/Android/Sdk/platform-tools/adb}"
PKG=dev.ncode.sp404layers
ACT="$PKG/.MainActivity"
PORT=9333
ATTEMPTS=3
# Not the cause of the denial, but a locked phone cannot be checked visually
# and input keyevents will not reach the app.
ensure_awake() {
"$ADB" shell input keyevent KEYCODE_WAKEUP >/dev/null 2>&1 || true
"$ADB" shell input keyevent 82 >/dev/null 2>&1 || true
local wake lock
wake=$("$ADB" shell dumpsys power | grep -m1 -o 'mWakefulness=[A-Za-z]*' | cut -d= -f2 | tr -d '\r')
lock=$("$ADB" shell dumpsys window | grep -m1 -o 'mDreamingLockscreen=[a-z]*' | cut -d= -f2 | tr -d '\r')
if [ "$wake" != "Awake" ] || [ "$lock" = "true" ]; then
echo "" >&2
echo " #############################################################" >&2
echo " # UNLOCK THE PHONE #" >&2
echo " # wakefulness=$wake lockscreen=$lock" >&2
echo " # Nothing on screen can be verified while it is locked. #" >&2
echo " #############################################################" >&2
echo "" >&2
return 1
fi
echo "device awake and unlocked"
}
keep_awake() {
# `usb` writes stay_on_while_plugged_in=2 (USB only) and this phone charges
# over AC, so it silently never engages. `true` sets every charging-source bit:
# measured as 15 here (AC|USB|wireless|dock), not the 7 that older notes claim.
"$ADB" shell svc power stayon true >/dev/null
echo "stay-awake while charging: ON (revert with: scripts/device.sh sleep-normal)"
}
sleep_normal() {
"$ADB" shell svc power stayon false >/dev/null
echo "stay-awake: reverted to system default"
}
# Release builds are not DEBUGGABLE, so Capacitor never calls
# setWebContentsDebuggingEnabled and the devtools socket is never opened. Asking
# for it anyway reports "renderer denied" for a perfectly healthy app.
is_debuggable() {
"$ADB" shell dumpsys package "$PKG" | grep -q DEBUGGABLE
}
# Device-side clock, for bounding logcat to this launch. The format has to be
# quoted as one argument: toybox date rejects a bare `+FMT` after `adb shell`.
device_now() {
"$ADB" shell "date +'%m-%d %H:%M:%S.000'" | tr -d '\r'
}
# Did the renderer get denied since $1? These lines are emitted whether or not
# the build is debuggable, and the package filter matters - other apps are
# denied constantly and their lines mean nothing here.
denied_since() {
"$ADB" logcat -d -T "$1" 2>/dev/null \
| grep -E "AppBootManager.*Caller\[$PKG\]|cr_ChildProcessConn: Failed" \
| grep -v 'Result\[true\]'
}
# Wait until the page is actually parsed, not merely until the activity is up:
# the activity starts fine even when the renderer was denied.
page_loaded() {
local pid sock i
# Read the pid AFTER starting. `adb install -r` kills the old process, so a pid
# captured earlier points at a dead one and the socket lookup fails while the
# app itself is healthy - that produced one false "page never loaded".
pid=$("$ADB" shell pidof "$PKG" | tr -d '\r')
[ -n "$pid" ] || return 1
# Release build: the page cannot be inspected from here at all, so prove what
# can be proved - the process survived and nothing denied its renderer. A
# blank grey window is indistinguishable from a working one over adb, which is
# why this says "assumed" rather than claiming the page loaded.
if ! is_debuggable; then
sleep 2
[ -n "$("$ADB" shell pidof "$PKG" | tr -d '\r')" ] || return 1
[ -z "$(denied_since "$1")" ] || return 1
echo "app running (pid $pid, release build: page load assumed, not verified)"
return 0
fi
for i in $(seq 1 40); do
sock=$("$ADB" shell cat /proc/net/unix | grep -o "webview_devtools_remote_$pid" | head -1)
if [ -n "$sock" ]; then
"$ADB" forward "tcp:$PORT" "localabstract:$sock" >/dev/null 2>&1 || true
if curl -s --max-time 1 "http://127.0.0.1:$PORT/json" | grep -q 'SP-404'; then
echo "app loaded (pid $pid, $i polls)"
return 0
fi
fi
done
return 1
}
launch() {
ensure_awake || return 1
local attempt started
for attempt in $(seq 1 $ATTEMPTS); do
started=$(device_now)
"$ADB" shell am force-stop "$PKG"
"$ADB" shell am start -W -n "$ACT" >/dev/null
if page_loaded "$started"; then
[ "$attempt" -gt 1 ] && echo "(took $attempt attempts - post-install renderer denial)"
return 0
fi
echo "attempt $attempt: no page, retrying" >&2
done
echo "ERROR: page never loaded after $ATTEMPTS attempts." >&2
# Bounded to this launch and to our package. Unfiltered, this printed other
# apps' denials from minutes earlier and sent one debugging session chasing a
# denial that had never happened to us.
denied_since "$started" | tail -4 >&2
return 1
}
case "${1:-}" in
awake) ensure_awake ;;
keep-awake) keep_awake ;;
sleep-normal) sleep_normal ;;
launch) launch ;;
*) echo "usage: $0 {awake|keep-awake|sleep-normal|launch}" >&2; exit 2 ;;
esac