#!/usr/bin/env bash # Device helpers for driving the app over adb. # # TCL's AppBootManagerService refuses to let the app bind the WebView's renderer # process while the display is dozing: # # W/AppBootManager: Result[false]; Target[...SandboxedProcessService0]; # Caller[dev.ncode.sp404layers]; Reason[screen_off & caller care] # # 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. # # So: always ensure the screen is awake before launching, and say so plainly # when it is not, rather than leaving a blank window to puzzle over. set -euo pipefail ADB="${ADB:-$HOME/Android/Sdk/platform-tools/adb}" PKG=dev.ncode.sp404layers ACT="$PKG/.MainActivity" PORT=9333 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 " # #" >&2 echo " # wakefulness=$wake lockscreen=$lock" >&2 echo " # #" >&2 echo " # While the screen is off, TCL denies the WebView renderer and #" >&2 echo " # the app shows a blank window with no error anywhere. #" >&2 echo " ####################################################################" >&2 echo "" >&2 return 1 fi echo "device awake and unlocked" } keep_awake() { "$ADB" shell svc power stayon usb >/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" } # Force-stop, start, and wait until the page is actually parsed - not merely # until the activity is up, which happens even when the renderer was denied. launch() { ensure_awake || return 1 "$ADB" shell am force-stop "$PKG" "$ADB" shell am start -W -n "$ACT" >/dev/null local pid sock i # Read the pid AFTER starting: an `adb install -r` kills the old process, and # a stale pid makes the devtools socket lookup fail while the app is fine. pid=$("$ADB" shell pidof "$PKG" | tr -d '\r') for i in $(seq 1 60); 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 echo "ERROR: activity started but the page never loaded." >&2 echo "Renderer denials in the log:" >&2 "$ADB" logcat -d | grep -E 'AppBootManager.*Result\[false\]|ChildProcessConn: Failed' | 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