diff --git a/AGENTS.md b/AGENTS.md index 02b198e..bc283be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,12 +5,7 @@ for TV) that talks to a PeerTube instance over its REST API v1. ## Environment facts -- Host is Arch Linux. The Android SDK the build actually uses is `/opt/android-sdk` - (`ANDROID_SDK_ROOT` — note `local.properties`' `sdk.dir` points to the outdated - `~/Android/Sdk`, which doesn't exist; platforms 34/35/36, build-tools). -- Emulator testing (host emulator + agent container over adb): see - `docs/agent-container.md` — covers start script, ports, and what to reinstall - after a container rebuild. +- Host is Arch Linux with Android SDK at `~/Android/Sdk` (platforms 34/35/36, build-tools). - The Gradle wrapper is checked in (`gradlew` + `gradle/wrapper/`), so builds need only a JDK 17+ (`java`) on PATH. If `java` is missing, tell the user — do not install things on the host without asking. diff --git a/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeRows.kt b/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeRows.kt index dbd71d4..97aa411 100644 --- a/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeRows.kt +++ b/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeRows.kt @@ -78,10 +78,9 @@ class HomeRowsViewModel(private val repository: PeerTubeRepository) : ViewModel( fetchContinue() } - /** Re-fetch after playback changed server-side positions (UX report BUG-7). - * Keeps the current rows in place while re-fetching (clearing them disposed - * the rows and killed the focus-restore effect before it could run, #2). */ + /** Re-fetch after playback changed server-side positions (UX report BUG-7). */ fun refresh() { + continueItems = emptyList() loaded = true fetchContinue() fetchSubs() diff --git a/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt b/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt index 4efb9a0..baa5f2a 100644 --- a/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt +++ b/app/src/main/java/net/jeena/jtubetv/ui/screens/HomeScreen.kt @@ -87,14 +87,8 @@ fun HomeScreen(viewModel: AppViewModel = androidx.lifecycle.viewmodel.compose.vi // After BACK from the player: bump the restore token so lists re-focus the opened // card and re-fetch positions (UX report BUG-3 + BUG-7). - var hadPlaying by rememberSaveable { mutableStateOf(false) } LaunchedEffect(playingVideo) { - if (playingVideo != null) { - hadPlaying = true - } else if (hadPlaying) { - restoreToken++ - hadPlaying = false - } + if (playingVideo == null && restoreToken >= 1) restoreToken++ } // BACK order (TV standard): player (own handler) → drawer close → channel diff --git a/docs/agent-container.md b/docs/agent-container.md deleted file mode 100644 index 48a15d2..0000000 --- a/docs/agent-container.md +++ /dev/null @@ -1,60 +0,0 @@ -# Agent container ↔ host emulator setup - -How the testing loop between the JTube TV agent container and the host -emulator works, and what needs to exist again after a rebuild. - -## Topology - -- **Emulator**: runs on the **host** (`/opt/android-sdk`, launched by - `scripts/start-emulator.sh`). **Headless is the default**; set - `JTUBE_EMU_MODE=windowed` if you want the Qt window (e.g. for desktop - interaction testing). View it via scrcpy either way. -- **Agent container**: image `agent-container:latest`, container name pattern - `ac--`, launched with **`--network host`** — it shares the - host's network namespace. This is load-bearing: see below. - -## Why host networking makes adb trivial - -With `--network host`, the container's `localhost` *is* the host's localhost. -So inside the container: - -- `adb connect 127.0.0.1:5556` → hits the host's socat bridge → emulator adbd. -- The adb **server** running on 127.0.0.1:5037 is also shared with the host, - so the container inherits whatever authorization the host adb server has. - No separate RSA handshake is needed. (Caveat: whoever's adb server grabs - port 5037 first owns it; all clients share it.) - -## What must be (re)installed after a container rebuild - -1. **`android-tools` in the container** (provides `adb`; installed into the - running container with `sudo pacman -S --noconfirm android-tools`, but that - is ephemeral). To make rebuilds self-sufficient, bake it into the - `agent-container` image at build time. -2. **Nothing else is needed for adb**: key handling is inherited from the - shared host adb server (see above). If the container is ever switched to - bridge networking, use `adb connect rutherford:5556` — remote keys are - handled by the emulator's `ADB_VENDOR_KEYS` (next section). - -## Emulator-side facts (host) - -- Ports: console `5600`, adbd `5601` (`-port 5600` ⇒ console+1), LAN bridge - `socat TCP-LISTEN:5556 → 127.0.0.1:5601` (0.0.0.0, so remote machines can - `adb connect rutherford:5556`). -- Remote key whitelisting: the TV system image is a user build — adb - `push`/`shell` writes to `/data/misc/adb/adb_keys` are blocked. The script - therefore builds `/tmp/jtube-adbkeys-combined` (host key + every - `/tmp/jtube-adbkeys/*.pub`) and launches the emulator with - `ADB_VENDOR_KEYS` pointing at it. To trust a new client: put its - `~/.android/adbkey.pub` into `/tmp/jtube-adbkeys/` **before** launching. -- Logs: `/tmp/jtube-emu.log` (QEMU) and `/tmp/jtube-emu-run.log` (script). -- Viewing: `scrcpy -s emulator-5600` on the host, or from any LAN machine: - `adb connect rutherford:5556 && scrcpy -s 127.0.0.1:5556`. - -## Known quirks - -- Emulator Qt toolbar (back/home buttons) is a detached XWayland window that - misbehaves on Hyprland — drive the emulator via scrcpy or adb instead. -- The TV image is a user build: no `adb root`. -- Under the Android TV AVD, keyboard input in the Qt window works, but the - reliable inputs are scrcpy (`b` = back, `h` = home) or - `adb shell input keyevent`. \ No newline at end of file diff --git a/scripts/start-emulator.sh b/scripts/start-emulator.sh index 5995100..c10e04c 100755 --- a/scripts/start-emulator.sh +++ b/scripts/start-emulator.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash # # start-emulator.sh — boot the JTube TV test emulator on the HOST (not inside the -# agent container), with GPU acceleration, headless by default (drive it via -# adb/scrcpy). JTUBE_EMU_MODE=windowed shows the Qt window. +# agent container), with GPU acceleration and a window. Use this when the app should +# be tested over scrcpy (ATD/system quirks make scrcpy fail against headless +# in-container emulators) or when you simply want to watch it. # # Boots on console :5600 (adbd :5601); the agent container bridges # rutherford:5556 -> 127.0.0.1:5601 with socat, so after this script the emulator is @@ -53,41 +54,22 @@ for pid in $(ps aux | awk '/[q]emu-system-x86_64/ {print $2}' 2>/dev/null); do done sleep 1 -MODE="${JTUBE_EMU_MODE:-headless}" # headless (default) | windowed -WINDOWED=1 ACCEL=() -if [ "$MODE" = windowed ]; then - if [ -n "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ]; then - echo "windowed (JTUBE_EMU_MODE=windowed) — emulator window + GPU host" - else - echo "JTUBE_EMU_MODE=windowed but no display detected — falling back to headless" - WINDOWED=0 - ACCEL+=(-no-window) - fi +WINDOWED=1 +if [ -n "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ]; then + echo "desktop session detected — emulator window + GPU host" else WINDOWED=0 ACCEL+=(-no-window) - echo "headless (default; JTUBE_EMU_MODE=windowed for a window) — no emulator window" + echo "headless (no display) — running without a window, swiftshader rendering" fi if [ -e /dev/kvm ]; then - ACCEL+=(-accel on) + [ $WINDOWED -eq 1 ] && ACCEL+=(-accel kvm) echo "/dev/kvm found — hardware acceleration on" fi -# --- build combined adb vendor-keys file ------------------------------------ -# The TV system image is a user build: adb push to /data/misc/adb/adb_keys is -# blocked, so remote keys are trusted via ADB_VENDOR_KEYS at launch instead. -# Drop extra public keys into /tmp/jtube-adbkeys/ (one per file), e.g. from the -# agent container: docker exec sh -c 'cat ~/.android/adbkey.pub' > /tmp/jtube-adbkeys/agent-container.pub -VKEYS=/tmp/jtube-adbkeys-combined -{ - cat "$HOME/.android/adbkey.pub" - cat /tmp/jtube-adbkeys/*.pub 2>/dev/null -} > "$VKEYS" -chmod 600 "$VKEYS" - echo "Booting AVD '$AVD' …" -ADB_VENDOR_KEYS="$VKEYS" setsid nohup "$EMU" -avd "$AVD" -port "$PORT" -gpu host "${ACCEL[@]:-}" \ +setsid nohup "$EMU" -avd "$AVD" -port "$PORT" -gpu host "${ACCEL[@]:-}" \ -no-snapshot > /tmp/jtube-emu.log 2>&1 < /dev/null & echo "emulator PID $! — log: /tmp/jtube-emu.log" @@ -97,6 +79,21 @@ for i in $(seq 1 90); do sleep 5 done +# --- authorize remote adb clients headlessly --------------------------------- +# The host console transport is always trusted; use it to whitelist client keys +# so `adb connect rutherford:5556` from other machines works without a popup. +# Drop extra public keys into /tmp/jtube-adbkeys/ (one per file), e.g. from +# Chester: cat ~/.android/adbkey.pub > /tmp/jtube-adbkeys/chester.pub +"$ADB" -s "emulator-$PORT" root > /dev/null 2>&1 || true +for key_file in "$HOME/.android/adbkey.pub" /tmp/jtube-adbkeys/*.pub; do + [ -f "$key_file" ] || continue + "$ADB" -s "emulator-$PORT" push "$key_file" "/data/misc/adb/adb_keys" > /dev/null 2>&1 || \ + "$ADB" -s "emulator-$PORT" shell "cat > /data/misc/adb/adb_keys" < "$key_file" || { + echo "could not whitelist $key_file"; + } +done +"$ADB" -s "emulator-$PORT" shell "setprop service.adb.tcp.port 5601; stop adbd; start adbd" > /dev/null 2>&1 || true + # --- the bridge: advertise adbd on the LAN as :5556 --------------------------- if ! ss -tln 2>/dev/null | grep -q ":5556"; then if command -v socat > /dev/null; then