Compose app mirroring local-updater's stack (single :androidApp module, pinned CA, HttpURLConnection transport) plus what this app needs on top: a bearer token sealed with an Android Keystore AES-GCM key, an aiapp://enroll intent filter so scanning the server's terminal QR with the stock camera enrolls the phone with no QR library, an SSE client that resumes by transcript cursor, and a transcript renderer folding the common event model into user bubbles, streaming text, collapsible tool cards, and answerable question cards. Verified on the tdep emulator against the real server: enrollment deep link, list, spawn, streamed echo turn, question answer round trip, tool card expansion, adjustResize keyboard behavior. Build is warning-clean (compose.* accessors replaced with direct dependencies). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
107 lines
3.6 KiB
Bash
Executable File
107 lines
3.6 KiB
Bash
Executable File
#!/bin/sh
|
|
# Builds and runs this app on an emulator, creating/booting the AVD first if
|
|
# it isn't already up. Same flow as local-updater's run-android.sh; see that
|
|
# script for the reasoning behind the avd handling.
|
|
#
|
|
# Environment setup (SDK location, PATH, ...) lives in ./android-env.sh,
|
|
# which can also be sourced directly for one-off commands.
|
|
set -eu
|
|
|
|
APP_ID="com.example.aiapp"
|
|
|
|
# The AVD shared by this machine's Android projects -- one emulator, not
|
|
# one per repo. Override with AVD_NAME=... elsewhere.
|
|
AVD_NAME="${AVD_NAME:-tdep}"
|
|
DEVICE_PROFILE="${DEVICE_PROFILE:-pixel_10}"
|
|
SYSTEM_IMAGE="${SYSTEM_IMAGE:-system-images;android-36;google_apis;x86_64}"
|
|
|
|
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
cd "$SCRIPT_DIR"
|
|
|
|
# shellcheck source=./android-env.sh
|
|
. ./android-env.sh
|
|
|
|
# Prints the adb serial of a running instance of AVD "$1", or nothing.
|
|
avd_serial() {
|
|
for s in $(adb devices | awk '$2 == "device" {print $1}'); do
|
|
if [ "$(adb -s "$s" emu avd name 2>/dev/null | head -n1 | tr -d '\r')" = "$1" ]; then
|
|
echo "$s"
|
|
return 0
|
|
fi
|
|
done
|
|
}
|
|
|
|
echo "==> Ensuring emulator system image is installed"
|
|
android sdk install emulator "$SYSTEM_IMAGE" || echo " (non-fatal: see above)"
|
|
|
|
if [ ! -f "$ANDROID_AVD_HOME/$AVD_NAME.ini" ]; then
|
|
echo "==> Creating AVD '$AVD_NAME' ($DEVICE_PROFILE, $SYSTEM_IMAGE)"
|
|
echo no | avdmanager create avd \
|
|
-n "$AVD_NAME" \
|
|
-k "$SYSTEM_IMAGE" \
|
|
--device "$DEVICE_PROFILE" \
|
|
--sdcard 512M
|
|
else
|
|
echo "==> Reusing existing AVD '$AVD_NAME'"
|
|
fi
|
|
|
|
# Host keyboard into the emulator -- this app has text fields.
|
|
CONFIG_INI="$ANDROID_AVD_HOME/$AVD_NAME.avd/config.ini"
|
|
if [ -f "$CONFIG_INI" ]; then
|
|
grep -v '^hw\.keyboard=' "$CONFIG_INI" >"$CONFIG_INI.tmp"
|
|
echo "hw.keyboard=yes" >>"$CONFIG_INI.tmp"
|
|
mv "$CONFIG_INI.tmp" "$CONFIG_INI"
|
|
fi
|
|
|
|
SERIAL=$(avd_serial "$AVD_NAME")
|
|
if [ -n "$SERIAL" ]; then
|
|
echo "==> Emulator '$AVD_NAME' already running ($SERIAL)"
|
|
else
|
|
# Clean up a stray/crashed process for this AVD, if any. The bracketed
|
|
# first character keeps the pattern from matching the shell running
|
|
# this script -- unbracketed, this kills that shell mid-run.
|
|
pkill -f "[e]mulator.*-avd $AVD_NAME" >/dev/null 2>&1 || true
|
|
|
|
EMU_LOG="/tmp/$AVD_NAME-emulator.log"
|
|
: >"$EMU_LOG"
|
|
if [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then
|
|
echo "==> Starting emulator '$AVD_NAME' with GPU acceleration (-gpu host)"
|
|
emulator -avd "$AVD_NAME" -gpu host -no-audio >"$EMU_LOG" 2>&1 &
|
|
else
|
|
echo "==> No display -- starting emulator '$AVD_NAME' headless (-gpu swiftshader_indirect)"
|
|
emulator -avd "$AVD_NAME" -gpu swiftshader_indirect -no-audio -no-window \
|
|
>"$EMU_LOG" 2>&1 &
|
|
fi
|
|
EMU_PID=$!
|
|
|
|
i=0
|
|
booted=""
|
|
while [ "$i" -lt 150 ]; do
|
|
if ! kill -0 "$EMU_PID" 2>/dev/null; then
|
|
echo "Emulator process exited unexpectedly. Log output:" >&2
|
|
cat "$EMU_LOG" >&2
|
|
exit 1
|
|
fi
|
|
SERIAL=$(avd_serial "$AVD_NAME")
|
|
if [ -n "$SERIAL" ]; then
|
|
booted=$(adb -s "$SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')
|
|
[ "$booted" = "1" ] && break
|
|
fi
|
|
i=$((i + 1))
|
|
sleep 2
|
|
done
|
|
if [ "$booted" != "1" ]; then
|
|
echo "Emulator did not finish booting in time. Log output:" >&2
|
|
cat "$EMU_LOG" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo "==> Building debug APK"
|
|
./gradlew :androidApp:assembleDebug
|
|
|
|
APK="androidApp/build/outputs/apk/debug/androidApp-debug.apk"
|
|
echo "==> Installing and launching $APK"
|
|
adb -s "$SERIAL" install -r "$APK"
|
|
adb -s "$SERIAL" shell am start -n "$APP_ID/.MainActivity"
|