#!/usr/bin/env bash # Build (and optionally install) the HRM Android APK. # # Usage: # ./scripts/build.sh [phone|emu] # build + install (default: phone) # ./scripts/build.sh phone --no-install # build only # # Output: hrm/hrm.apk # # Prereqs on this workstation (the script checks each and explains what is # missing): # - JDK (java on PATH) # - Go + gogio v0.10.0 (~/go/bin) # - Android SDK at ~/android-sdk (platform-tools, build-tools 35.0.0, # NDK for gogio) # - debug keystore at ~/.android/debug.keystore # apktool is not a prerequisite: if it is missing, the script downloads # v3.0.3 to the stable location ~/android-sdk/tools/apktool.jar. # # Why the apktool detour: gogio's manifest permissions come from a hardcoded # table and only declare the pre-Android-12 BLUETOOTH/BLUETOOTH_ADMIN # permissions. With targetSdk 31+ those are no-ops and BLE needs the # runtime permissions BLUETOOTH_SCAN and BLUETOOTH_CONNECT declared in the # manifest. So we decode the gogio APK, inject the two permissions into # AndroidManifest.xml, rebuild, and re-sign with the debug key. (The # runtime request and scan gating are implemented in Go: see # hrm/perm_android.go.) set -euo pipefail REPO=$(cd "$(dirname "$0")/.." && pwd) export ANDROID_HOME=${ANDROID_HOME:-$HOME/android-sdk} export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/build-tools/35.0.0:$HOME/go/bin MODE=${1:-phone} INSTALL=1 [ "${2:-}" = "--no-install" ] && INSTALL=0 case "$MODE" in phone) ARCH="arm64,arm";; emu) ARCH="amd64";; *) echo "usage: $0 [phone|emu] [--no-install]" >&2; exit 1;; esac die() { echo "ERROR: $*" >&2; exit 1; } command -v java >/dev/null 2>&1 || die "java not found" command -v gogio >/dev/null 2>&1 || die "gogio not found (need v0.10.0 in ~/go/bin)" command -v apksigner >/dev/null 2>&1 || die "apksigner not found (need $ANDROID_HOME/build-tools/35.0.0)" command -v adb >/dev/null 2>&1 || die "adb not found (need $ANDROID_HOME/platform-tools)" [ -f "$HOME/.android/debug.keystore" ] || die "debug keystore missing: $HOME/.android/debug.keystore" # apktool: auto-download to a stable location if missing. APKTOOL="$ANDROID_HOME/tools/apktool.jar" if [ ! -f "$APKTOOL" ]; then echo "=== apktool missing; downloading v3.0.3 to $APKTOOL ===" mkdir -p "$ANDROID_HOME/tools" curl -fsSL -o "$APKTOOL" \ https://github.com/iBotPeaches/Apktool/releases/download/v3.0.3/apktool_3.0.3.jar \ || die "apktool download failed" fi WORK=$(mktemp -d) trap 'rm -rf "$WORK"' EXIT echo "=== gogio (Go -> APK) ===" # targetSdk 34: targeting 35 makes Android 15 force edge-to-edge, which the # app does not handle. (cd "$REPO/hrm" && gogio -target android -targetsdk 34 -arch "$ARCH" -o "$WORK/hrm-raw.apk" .) [ -f "$WORK/hrm-raw.apk" ] || die "gogio did not produce an APK" echo "=== apktool decode + inject BLE permissions ===" # gogio's manifest permissions come from a hardcoded table and only declare # the pre-Android-12 permissions; the runtime permission logic itself lives # in the Go code (hrm/perm_android.go). java -jar "$APKTOOL" d "$WORK/hrm-raw.apk" -o "$WORK/decoded" -f >/dev/null python3 - "$WORK/decoded/AndroidManifest.xml" << 'PYEOF' import sys, pathlib p = pathlib.Path(sys.argv[1]) t = p.read_text() perms = [ '', '', ] for perm in perms: if perm not in t: t = t.replace(" /dev/null echo "=== sign ===" apksigner sign \ --ks "$HOME/.android/debug.keystore" --ks-pass pass:android \ --key-pass pass:android --ks-key-alias androiddebugkey \ --out "$REPO/hrm/hrm.apk" "$WORK/hrm-unsigned.apk" # ADB_SERIAL selects the device when several are attached (adb devices). ADB="adb${ADB_SERIAL:+ -s $ADB_SERIAL}" if [ "$INSTALL" = "1" ]; then echo "=== install ===" $ADB get-state >/dev/null 2>&1 || die "no adb device (start the emulator or plug in a phone; set ADB_SERIAL if several are attached)" $ADB install -r "$REPO/hrm/hrm.apk" fi echo "=== DONE: $REPO/hrm/hrm.apk ==="