# The Android Auto prober A throwaway app that binds SoulStream's `MediaBrowserService` the way Android Auto does, walks whatever tree comes back, and prints one JSON object per event under the logcat tag `SoulAutoProbe`. ```bash bash tools/auto-prober/probe.sh # depth 2, the ordinary root bash tools/auto-prober/probe.sh 3 true # depth 3, the RECENT root adb uninstall com.soulstream.autoprober # and it is gone ``` ## Why it exists Android Auto reaches a media app through `MediaBrowserService`, with the app's process cold — no Activity, no WebView, and therefore no DevTools socket. `tests/mobile/smoke.mjs` drives the phone over CDP, so it cannot see any of this: with SoulStream force-stopped there is nothing for it to attach to. Without a head unit or the Desktop Head Unit, this is the only instrument the project has. It is framework-only on purpose — `android.media.browse.MediaBrowser`, not androidx, not media3. That is the path Auto itself takes into a media3 app (media3 serves the legacy `android.media.browse.MediaBrowserService` action through `MediaSessionService.onBind`), and a prober built out of the same library as the thing it probes would not prove that path works. ## Where it lives, and why not in the generated project `src-tauri/gen/android/` is regenerated by `npx tauri android init` and is gitignored; anything put in there is deleted the next time someone runs it. So the prober is its own Gradle build, tracked, outside it — and it borrows the generated project's wrapper rather than shipping one, which pins Gradle 8.14.3, AGP 8.11.0, Kotlin 1.9.25 and JDK 17: exactly what is already in the Gradle cache, so a probe run downloads nothing. There is deliberately **no `gradle-wrapper.jar`** in this directory. ## What it prints One line per event, each a JSON object, all under `SoulAutoProbe:I`: | `ev` | what it says | | --- | --- | | `start` | the package, component, depth and whether the RECENT root was asked for | | `desc` | the `com.google.android.gms.car.application` meta-data resolved to a resource id, that resource's name, and the `` inside it | | `filter` | what `queryIntentServices` resolves for the legacy browse action and for media3's own, scoped to SoulStream | | `root` | **the number this exists for**: milliseconds from `connect()` to `onConnected()` with the process cold, the root media id, and whether a session token came with it | | `children` | one per subscription: the parent, how many rows, and each row's id, title, browsable and playable | | `fail` | the bind failed, was suspended, or the 8 s watchdog fired | | `done` | the walk finished: elapsed, nodes, leaves | `desc` is the important one for the manifest, because `adb shell pm dump` prints resolver tables and package state and **no application meta-data at all** — the phone walk's `auto-browse` and `auto-library` checks can prove the intent filters and nothing else. This resolves the meta-data int through `PackageManager.GET_META_DATA` and then opens the resource out of the app's own `Resources`, which is the only way to tell a real declaration from a dangling id without unpacking the APK. `probe.sh` also prints two lines of SoulStream's own log: `auto root …`, the service's view of the same connection (including which root hints survived the trip), and `auto service created n=…`, which is the androidx/media #3158 counter — see below. ## androidx/media #3158 Open since 2026-04-03, still open, reproduced on media3 1.11.0: a host that binds a `MediaLibraryService` while nothing is playing can put the service into an `onCreate → onBind → onGetSession → onConnect → onDestroy` loop at about thirty cycles a second. It is 100% reproducible after a force-stop with no existing binding — which is precisely what this prober does, so **expect to see it here first**. `MediaHub` holds the session, the player, the notification provider, the audio-focus request and the becoming-noisy receiver for the life of the process, so a cycle allocates and registers nothing and the loop is survivable. The loop itself is Android Auto's to fix and media3 has said it will not act first. `auto service created n=…` is how you find out: one or two is normal, a number climbing while a probe or a car is connected is the bug. ## What it is not It does not prove the car screen. Nothing here can: the Desktop Head Unit or a real head unit is the only thing that shows what Auto actually draws, how it groups a tree, or whether a tap plays. This proves the app answers. ## Safety No permissions beyond ``, no network, no storage, no transport command. Every exit path disconnects the browser and `onDestroy` disconnects again, so nothing is left bound to SoulStream's service. The only force-stop `probe.sh` issues is of `com.soulstream.app`, which `tests/mobile/smoke.mjs` already does on every phone run.