hrm/scripts/build.sh
Greg Pomerantz 689990639d Migrate to gioui.org v0.10.2; fix Android BLE flow, rendering, and circle.
Main changes:
- go.mod: gioui.org v0.10.2, go 1.24, git.wow.st/gmp/ble
  e53c7be (commitNowAllowingStateLoss + BLUETOOTH_SCAN/CONNECT
  permission request from BlessedConnect.onAttach).
- main.go: ported to the v0.10 API (app.Main + single-goroutine
  event loop, app.FrameEvent.Frame, material.* styles, op.Offset/
  Affine, clip.Path for the circle).
- On-demand rendering: no fixed 30 FPS ticker redraws. Frames are
  produced by Bluetooth events and input; 30 FPS only while the live
  stopwatch runs. The worker goroutine only polls the runtime
  permission (while a scan is pending) and starts the deferred scan.
- widget.Clickable.Clicked must be called before Layout in v0.10:
  the Clickable consumes its click events during Layout.
- circleClip: the clip.Path.Cube control points are relative to the
  pen; the second control point of each segment must be at (k-r),
  not (k). The old code drew a distorted, non-circular shape.
- Android: perm_android.go pins a worker goroutine to one OS thread
  attached to the JVM for the life of the app (repeated
  attach/detach stack-overflows ART); the BLE view handle and scan
  permission state are shared with the render loop under bleMu.
- os_android.go: AndroidViewEvent attach handling; os_others.go:
  no-op stubs for other platforms.

New:
- scripts/build.sh: gogio -> apktool decode -> inject
  BLUETOOTH_SCAN/BLUETOOTH_CONNECT into the manifest -> rebuild ->
  debug-sign -> install. Usage: ./scripts/build.sh [phone|emu]
  [--no-install].
- .gitignore: .DS_Store, *.idsig.
2026-08-22 07:50:05 -04:00

111 lines
4.4 KiB
Bash
Executable File

#!/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 = [
'<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>',
'<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>',
]
for perm in perms:
if perm not in t:
t = t.replace(" <application", " " + perm + "\n <application", 1)
if perm not in t:
sys.exit("ERROR: failed to inject " + perm)
p.write_text(t)
print("manifest: BLUETOOTH_SCAN + BLUETOOTH_CONNECT present")
PYEOF
echo "=== apktool rebuild ==="
java -jar "$APKTOOL" b "$WORK/decoded" -o "$WORK/hrm-unsigned.apk" >/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 ==="