Files
SoulStream/tools/auto-prober/probe.sh
T
KeelrLeek 54245a33b5 android auto: Recent means albums and playlists, and is never empty over a full library
"Recent" answered with TRACKS out of the `plays` table, and on a phone whose
history had just been wiped that was "No items" sitting over a full library —
the first thing a driver sees, saying the app has nothing.

Three changes, each about what the word means in a car.

**Albums, not tracks.** "Recent" in a car is "put that back on", and what
anyone means by that is the record. A list of single songs plays one and stops.
`plays` records what was played by NAME rather than by track id, so getting
back to an album means matching the pair again; anything played that has since
left the library drops out of the join, which is right for a row whose promise
is that tapping it works.

**Playlists, which were absent entirely.** They are in the database, they are
local, and they play from cold exactly as albums do — they were missing only
because the four root slots were spent, and Recent is somewhere they can live
without taking one. Counted out of `playlist_items` rather than
`playlist_tracks`: it is the richer table and the one that knows whether an
entry is a local file or something that needs the resolver. A playlist that is
part catalog says so on the row — "Playlist · 4 of 9 here" — so the gap is
declared before the tap rather than discovered after it, and tapping plays the
local entries in order. Entries that need the phone are shown rather than
hidden, so the car's list matches what is on the phone, and marked unplayable
rather than pretending.

**An unplayed library is not an empty shelf.** With no history at all, Recent
falls back to the newest albums, which is a defensible reading of the word and
a great deal better than an empty tab on a fresh install.

`ss:playlist:<id>` and `ss:pitem:<id>` join the tree and `resolve_playable`, so
a playlist or one of its tracks plays with the rest behind it.

Two fixes to the instrument that found this: `probe.sh` was passing
`--es play ""` when no query was given, which `am start` rejects outright
("Argument expected after play") — it is conditional now. And a splice left
`fn track_node(fn track_node(` in the source, which rustc reports as an
unclosed delimiter at the END of the file, pointing 400 lines away from the
mistake.
2026-09-15 15:08:21 -05:00

69 lines
3.0 KiB
Bash

#!/usr/bin/env bash
# Build, install and run the Android Auto prober, then print what it saw.
#
# bash tools/auto-prober/probe.sh # depth 2, the ordinary root
# bash tools/auto-prober/probe.sh 3 true # depth 3, the RECENT root
#
# This installs a SECOND app on the phone — com.soulstream.autoprober, about
# 50 KB, no permissions, no network, no storage, no UI. `adb uninstall
# com.soulstream.autoprober` removes it completely. It never signs in,
# downloads, joins a room, writes the config or touches the library; the only
# thing it force-stops is SoulStream, which the phone walk already does.
#
# Gradle comes from the GENERATED project's wrapper on purpose: that pins
# Gradle 8.14.3, AGP 8.11.0, Kotlin 1.9.25 and JDK 17, all of which are
# already in Kyle's Gradle cache, so this downloads nothing new. There is no
# gradle-wrapper.jar in this directory and there should not be one.
set -euo pipefail
ROOT=/f/Coding/SoulStream
cd "$ROOT"
source src-tauri/android/env.sh
DEPTH="${1:-2}"
RECENT="${2:-false}"
# A media id here plays it instead of walking the tree — the only way to prove
# a car's tap without a car. The run takes ~25s because the claim being tested
# is that the SECOND track starts on its own.
PLAY="${3:-}"
APK=tools/auto-prober/prober/build/outputs/apk/debug/prober-debug.apk
GRADLEW="$ROOT/src-tauri/gen/android/gradlew.bat"
test -f "$GRADLEW" || { echo "no generated Android project — run a normal build first"; exit 1; }
echo "== this installs com.soulstream.autoprober on the phone (adb uninstall com.soulstream.autoprober removes it)"
echo "== gradle $(date +%T)"
(
cd tools/auto-prober
MSYS_NO_PATHCONV=1 "$GRADLEW" :prober:assembleDebug -q 2>&1 | grep -v "^$" | tail -20 || true
)
test -f "$APK" || { echo "no prober APK — gradle's tail is above"; exit 1; }
ls -la "$APK"
echo "== install $(date +%T)"
"$ADB" install -r "$APK" > /dev/null
echo "== cold: force-stopping SoulStream $(date +%T)"
"$ADB" logcat -c
"$ADB" shell am force-stop com.soulstream.app
"$ADB" shell am start -W -n com.soulstream.autoprober/.ProbeActivity \
--ei depth "$DEPTH" --ez recent "$RECENT" ${PLAY:+--es play "$PLAY"} > /dev/null
if [ -n "$PLAY" ]; then sleep 25; else sleep 7; fi
echo
echo "== the probe (one JSON object per event)"
"$ADB" logcat -d -s SoulAutoProbe:I
echo
echo "== the root, as the service saw it"
"$ADB" logcat -d -s SoulStream:I | grep "auto root" | tail -3 || echo " (no root line — the callback was never reached)"
# androidx/media #3158: a host that binds with nothing playing can put the
# service in a create/destroy loop at ~30 Hz. The singleton in MediaHub means
# nothing is allocated per cycle, but the loop itself is Android Auto's bug
# to fix — so this number is the thing to watch, and it is why those two
# lifecycle log lines exist at all. One or two is normal.
echo
echo "== androidx/media #3158: how many times the service was built"
"$ADB" logcat -d -s SoulStream:I | grep "auto service created" | tail -1 \
|| echo " (no creation line — the service was never built)"