Compare commits

1 Commits
v1.0.0 ... main

Author SHA1 Message Date
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
2 changed files with 68 additions and 8 deletions

View File

@@ -76,7 +76,7 @@ Device notes), but nothing on screen can be verified through a lockscreen and
| Command | What it does | | Command | What it does |
|---|---| |---|---|
| `npm run device:keep-awake` | Stay awake while charging. Do this at the start of a session. | | `npm run device:keep-awake` | Stay awake while charging. Do this at the start of a session. |
| `npm run device:launch` | Check awake/unlocked, force-stop, start, wait for the page to actually parse | | `npm run device:launch` | Check awake/unlocked, force-stop, start, wait for the page (debug builds) |
| `npm run device:awake` | Just the check — prints a loud warning if locked | | `npm run device:awake` | Just the check — prints a loud warning if locked |
| `npm run device:sleep-normal` | Revert the stay-awake setting when finished | | `npm run device:sleep-normal` | Revert the stay-awake setting when finished |
@@ -84,6 +84,22 @@ Device notes), but nothing on screen can be verified through a lockscreen and
starts fine even when the renderer was denied. On failure it prints the starts fine even when the renderer was denied. On failure it prints the
`AppBootManager` denial lines rather than leaving a blank window to puzzle over. `AppBootManager` denial lines rather than leaving a blank window to puzzle over.
**On a release build it cannot do that, and says so.** A release APK is not
`DEBUGGABLE`, so Capacitor never calls `setWebContentsDebuggingEnabled` and
`webview_devtools_remote_<pid>` is never opened — the page simply cannot be
inspected over adb. Demanding that socket anyway made the helper report
`renderer denied` three times for a perfectly healthy app, force-stopping and
restarting it each time. It now checks that the process survived and that
nothing denied *our* renderer, then prints `page load assumed, not verified`. A
blank grey window and a working one are genuinely indistinguishable from here,
so that wording is the honest limit, not hedging.
The denial grep is filtered by package and bounded to the current launch
(`logcat -d -T`, timestamp from `adb shell "date +'%m-%d %H:%M:%S.000'"` — the
format must be one quoted argument or toybox `date` rejects it). Unfiltered, it
printed `com.xiaomi.wearable` denials from minutes earlier and sent one session
chasing a denial that had never happened to us.
It also reads the pid *after* starting the app. `adb install -r` kills the It also reads the pid *after* starting the app. `adb install -r` kills the
running process, so a pid captured earlier points at a dead process and the running process, so a pid captured earlier points at a dead process and the
devtools socket lookup fails while the app itself is healthy — that produced one devtools socket lookup fails while the app itself is healthy — that produced one
@@ -266,8 +282,10 @@ beats an afternoon of synthetic input.
**Stay awake needs `svc power stayon true`, not `usb`.** The `usb` form writes **Stay awake needs `svc power stayon true`, not `usb`.** The `usb` form writes
`stay_on_while_plugged_in = 2`, and this phone charges over AC (`USB powered: `stay_on_while_plugged_in = 2`, and this phone charges over AC (`USB powered:
false`), so it silently never engages — `mStayOn` stays false. `true` writes 7 false`), so it silently never engages — `mStayOn` stays false. `true` sets every
(any source) and works. The screen timeout is separate and was 30s: charging-source bit and works; measured as **15** here (AC|USB|wireless|dock),
not the 7 an earlier note claimed — 7 predates dock charging. The screen timeout
is separate and was 30s:
``` ```
adb shell svc power stayon true adb shell svc power stayon true

View File

@@ -49,7 +49,10 @@ ensure_awake() {
} }
keep_awake() { keep_awake() {
"$ADB" shell svc power stayon usb >/dev/null # `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)" echo "stay-awake while charging: ON (revert with: scripts/device.sh sleep-normal)"
} }
@@ -58,6 +61,28 @@ sleep_normal() {
echo "stay-awake: reverted to system default" 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: # Wait until the page is actually parsed, not merely until the activity is up:
# the activity starts fine even when the renderer was denied. # the activity starts fine even when the renderer was denied.
page_loaded() { page_loaded() {
@@ -67,6 +92,19 @@ page_loaded() {
# app itself is healthy - that produced one false "page never loaded". # app itself is healthy - that produced one false "page never loaded".
pid=$("$ADB" shell pidof "$PKG" | tr -d '\r') pid=$("$ADB" shell pidof "$PKG" | tr -d '\r')
[ -n "$pid" ] || return 1 [ -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 for i in $(seq 1 40); do
sock=$("$ADB" shell cat /proc/net/unix | grep -o "webview_devtools_remote_$pid" | head -1) sock=$("$ADB" shell cat /proc/net/unix | grep -o "webview_devtools_remote_$pid" | head -1)
if [ -n "$sock" ]; then if [ -n "$sock" ]; then
@@ -82,19 +120,23 @@ page_loaded() {
launch() { launch() {
ensure_awake || return 1 ensure_awake || return 1
local attempt local attempt started
for attempt in $(seq 1 $ATTEMPTS); do for attempt in $(seq 1 $ATTEMPTS); do
started=$(device_now)
"$ADB" shell am force-stop "$PKG" "$ADB" shell am force-stop "$PKG"
"$ADB" shell am start -W -n "$ACT" >/dev/null "$ADB" shell am start -W -n "$ACT" >/dev/null
if page_loaded; then if page_loaded "$started"; then
[ "$attempt" -gt 1 ] && echo "(took $attempt attempts - post-install renderer denial)" [ "$attempt" -gt 1 ] && echo "(took $attempt attempts - post-install renderer denial)"
return 0 return 0
fi fi
echo "attempt $attempt: renderer denied, retrying" >&2 echo "attempt $attempt: no page, retrying" >&2
done done
echo "ERROR: page never loaded after $ATTEMPTS attempts." >&2 echo "ERROR: page never loaded after $ATTEMPTS attempts." >&2
"$ADB" logcat -d | grep -E 'AppBootManager.*Result\[false\]|ChildProcessConn: Failed' | tail -4 >&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 return 1
} }