From 41adb897639a479eff4ef364b5a04cdc7637293e Mon Sep 17 00:00:00 2001 From: Brummel Date: Thu, 23 Apr 2026 12:29:11 +0200 Subject: [PATCH] chore(watch): add run.sh helper for build/install/run Inner-loop tooling so the dev cycle does not require Android Studio. Subcommands: build, install, start, stop, logcat, shot, clean (default = install + start). Auto-detects JAVA_HOME (bundled JBR) and adb. Boots an emulator on demand when no device is attached: interactive menu of available AVDs, with WEAR_AVD= env-var bypass for non-interactive use. Waits for sys.boot_completed (180s timeout) before installing. --- watch/wearos/run.sh | 226 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100755 watch/wearos/run.sh diff --git a/watch/wearos/run.sh b/watch/wearos/run.sh new file mode 100755 index 0000000..04d4c91 --- /dev/null +++ b/watch/wearos/run.sh @@ -0,0 +1,226 @@ +#!/usr/bin/env bash +# Build / install / run helper for the Wear OS app. +# +# Subcommands: +# (none) install + start (default inner loop) +# build ./gradlew :app:assembleDebug +# install ./gradlew :app:installDebug +# start adb shell am start ... +# stop adb shell am force-stop ... +# logcat adb logcat, filtered to the app's PID +# shot [path] screencap to PNG (default /tmp/wear_screen.png) +# clean ./gradlew clean +# help show this help +# +# Env vars: +# WEAR_AVD AVD name to auto-boot when no device is attached +# (skips the interactive menu; useful for CI / scripted runs) + +set -euo pipefail + +# --- Config ---------------------------------------------------------------- +PACKAGE="com.doctate.watch" +ACTIVITY=".presentation.MainActivity" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# --- Output helpers -------------------------------------------------------- +if [[ -t 1 ]]; then + C_INFO=$'\e[1;34m'; C_OK=$'\e[1;32m'; C_ERR=$'\e[1;31m'; C_OFF=$'\e[0m' +else + C_INFO=""; C_OK=""; C_ERR=""; C_OFF="" +fi +info() { printf "%s==>%s %s\n" "$C_INFO" "$C_OFF" "$*"; } +ok() { printf "%s OK%s %s\n" "$C_OK" "$C_OFF" "$*"; } +die() { printf "%sERR%s %s\n" "$C_ERR" "$C_OFF" "$*" >&2; exit 1; } + +# --- Toolchain detection --------------------------------------------------- +detect_java() { + if [[ -n "${JAVA_HOME:-}" && -x "$JAVA_HOME/bin/java" ]]; then + return + fi + for candidate in /opt/android-studio/jbr /usr/lib/jvm/java-21-openjdk; do + if [[ -x "$candidate/bin/java" ]]; then + export JAVA_HOME="$candidate" + return + fi + done + die "No JDK found. Set JAVA_HOME or install jdk21-openjdk." +} + +detect_adb() { + if [[ -n "${ADB:-}" && -x "$ADB" ]]; then return; fi + if [[ -x "$HOME/Android/Sdk/platform-tools/adb" ]]; then + ADB="$HOME/Android/Sdk/platform-tools/adb" + elif command -v adb >/dev/null 2>&1; then + ADB="$(command -v adb)" + else + die "adb not found. Expected ~/Android/Sdk/platform-tools/adb." + fi +} + +ensure_device() { + detect_adb + local count + count=$("$ADB" devices | awk 'NR>1 && $2=="device"' | wc -l) + if [[ "$count" -gt 0 ]]; then + if [[ "$count" -gt 1 ]]; then + info "Multiple devices attached; ADB will pick the first." + fi + return + fi + boot_emulator_or_die +} + +boot_emulator_or_die() { + local emulator_bin="$HOME/Android/Sdk/emulator/emulator" + if [[ ! -x "$emulator_bin" ]]; then + die "No device attached and emulator not found at $emulator_bin." + fi + + local -a avds + mapfile -t avds < <("$emulator_bin" -list-avds 2>/dev/null) + if [[ "${#avds[@]}" -eq 0 ]]; then + die "No device attached and no AVDs configured. Create one in Android Studio." + fi + + local chosen="" + + # Env-var override: skip the menu (CI / scripted runs). + if [[ -n "${WEAR_AVD:-}" ]]; then + local a + for a in "${avds[@]}"; do + if [[ "$a" == "$WEAR_AVD" ]]; then + chosen="$WEAR_AVD" + break + fi + done + if [[ -z "$chosen" ]]; then + die "WEAR_AVD='$WEAR_AVD' not in available AVDs: ${avds[*]}" + fi + elif [[ -t 0 ]]; then + info "No device attached. Available AVDs:" + local i + for i in "${!avds[@]}"; do + printf " %d) %s\n" "$((i+1))" "${avds[$i]}" + done + printf " 0) abort\n" + local choice + read -r -p "Start which? " choice + if [[ ! "$choice" =~ ^[0-9]+$ ]]; then + die "Invalid choice: '$choice' (expected a number)." + fi + if [[ "$choice" -eq 0 ]]; then + die "Aborted by user." + fi + if [[ "$choice" -lt 1 || "$choice" -gt "${#avds[@]}" ]]; then + die "Out of range: $choice (have ${#avds[@]} AVDs)." + fi + chosen="${avds[$((choice-1))]}" + else + die "No device attached. Set WEAR_AVD= or run interactively. Available: ${avds[*]}" + fi + + start_emulator_and_wait "$emulator_bin" "$chosen" +} + +start_emulator_and_wait() { + local emulator_bin="$1" + local avd="$2" + + info "Starting AVD '$avd' (background)..." + nohup "$emulator_bin" -avd "$avd" >/dev/null 2>&1 & + disown + + info "Waiting for ADB to see the device..." + "$ADB" wait-for-device + + info "Waiting for boot to complete (up to 180s)..." + local boot="" + local elapsed=0 + local timeout=180 + until [[ "$boot" == "1" ]]; do + if [[ "$elapsed" -ge "$timeout" ]]; then + die "Boot timeout after ${timeout}s (AVD '$avd')." + fi + sleep 2 + elapsed=$((elapsed + 2)) + boot=$("$ADB" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r' || true) + done + ok "AVD '$avd' is up (boot took ${elapsed}s)." +} + +# --- Subcommands ----------------------------------------------------------- +cmd_build() { + detect_java + info "Building debug APK..." + (cd "$SCRIPT_DIR" && ./gradlew :app:assembleDebug) + ok "Built." +} + +cmd_install() { + detect_java + ensure_device + info "Installing on device..." + (cd "$SCRIPT_DIR" && ./gradlew :app:installDebug) + ok "Installed." +} + +cmd_start() { + ensure_device + info "Starting $PACKAGE/$ACTIVITY..." + "$ADB" shell am start -n "$PACKAGE/$ACTIVITY" >/dev/null + ok "Started." +} + +cmd_stop() { + ensure_device + info "Stopping $PACKAGE..." + "$ADB" shell am force-stop "$PACKAGE" + ok "Stopped." +} + +cmd_logcat() { + ensure_device + local pid + pid=$("$ADB" shell pidof "$PACKAGE" | tr -d '\r' | awk '{print $1}') + if [[ -z "$pid" ]]; then + die "App $PACKAGE is not running. Use './run.sh start' first." + fi + info "Logcat for $PACKAGE (PID $pid). Ctrl-C to exit." + exec "$ADB" logcat --pid="$pid" +} + +cmd_shot() { + ensure_device + local out="${1:-/tmp/wear_screen.png}" + info "Screenshot -> $out" + "$ADB" exec-out screencap -p > "$out" + ok "Saved ($(wc -c < "$out") bytes)." +} + +cmd_clean() { + detect_java + info "Cleaning..." + (cd "$SCRIPT_DIR" && ./gradlew clean) + ok "Clean." +} + +cmd_help() { + # Print the leading comment block (lines 2..first non-comment line). + awk 'NR==1 { next } /^[^[:space:]#]/ { exit } { print }' "$0" \ + | sed 's/^# \{0,1\}//' +} + +# --- Dispatch -------------------------------------------------------------- +case "${1:-}" in + "") cmd_install; cmd_start ;; + build) cmd_build ;; + install) cmd_install ;; + start) cmd_start ;; + stop) cmd_stop ;; + logcat) cmd_logcat ;; + shot) shift; cmd_shot "$@" ;; + clean) cmd_clean ;; + -h|--help|help) cmd_help ;; + *) die "Unknown command: $1 (try './run.sh help')" ;; +esac