android: the handoff — the pending rounds, the pipeline, and what is unverified
Everything written this session that has not been through the device: rounds 43 to 48 (each patch adversarially verified, 43's transient-notice expectation fixed), a partial round 49, the pipeline scripts, the Android Auto design and the icon comparison mock. Also the two harness changes that were never A/B'd — adaptive waits, which may only shorten a declared wait and never lengthen it, and a ship that runs the mock walk and the deploy at the same time. The A/B and its 255-step baseline are in handoff/tools; main stays at what the phone has actually walked.
This commit is contained in:
1 parent
867fd42972
commit
9f0601027c
400 files changed
+26401
-3
No files matched your search
@@ -0,0 +1,85 @@
|
||||
## The tree in the car
|
||||
|
||||
Android Auto browses through a `MediaLibraryService`, and the system starts
|
||||
that service in a process the app has never been in: no Activity, no Tauri,
|
||||
no WebView, and therefore no frontend, no `#[tauri::command]` and no
|
||||
resolver. Everything on a car screen has to come from somewhere else. It
|
||||
comes from `src-tauri/src/auto.rs`, which opens `library.db` **read-only**
|
||||
and answers with its own SQL, and from
|
||||
`crates/tauri-plugin-media-session/…/LibraryBridge.kt`, which loads the
|
||||
native library, hands the JSON across and turns it into `MediaItem`s.
|
||||
|
||||
Cold start works because of three things that were already true.
|
||||
`JNI_OnLoad` (`android_glue.rs`) runs at `System.loadLibrary("app_lib")`,
|
||||
before any component exists, and publishes the JavaVM and the app Context;
|
||||
`crates/library` is plain rusqlite with no Tauri in it; `crates/player` is
|
||||
plain rodio with no Tauri in it. So a service-started process can load the
|
||||
`.so`, open the database and — in a later round — drive the engine.
|
||||
`LibraryBridge` is what calls `System.loadLibrary` there, because the app's
|
||||
only other loader (`com.soulstream.app.Rust`) needs an Activity to exist.
|
||||
|
||||
**The connection is read-only and that is not a detail.** `Library::open`
|
||||
runs `PRAGMA journal_mode = WAL` and eleven `CREATE TABLE IF NOT EXISTS`
|
||||
before it returns, which are writes into a database whose real owner may be
|
||||
running in another process at that moment. `auto.rs` uses
|
||||
`Connection::open_with_flags(…, SQLITE_OPEN_READ_ONLY)` with a 1.5-second
|
||||
busy timeout, and its statements are copied from `crates/library` rather
|
||||
than invented — including `album_tracks`'s disc ordering and
|
||||
`recent_albums`'s `link` CTE.
|
||||
|
||||
**Artists are grouped in Rust the way the phone groups them in JS.** There
|
||||
is no artists table; `src/mobile/lib.svelte.js` derives artists by folding
|
||||
`list_albums()` in its own order, keeping the FIRST casing it sees. A SQL
|
||||
`GROUP BY artist` would pick a different display name and the car would
|
||||
disagree with the phone in the same hand.
|
||||
|
||||
The node grammar, which is the contract the rounds after this one read:
|
||||
|
||||
| node | |
|
||||
| --- | --- |
|
||||
| `ss:root[?limit=N]` | the tabs; `N` is the browser's own root-children hint |
|
||||
| `ss:tab:recent` / `albums` / `artists` / `playlists` | browsable |
|
||||
| `ss:album:<albums.id>` | browsable → tracks |
|
||||
| `ss:artist:<pct-encoded lowercase name>` | browsable → albums |
|
||||
| `ss:playlist:<playlists.id>` | browsable → rows |
|
||||
| `ss:track:<tracks.id>` | playable; `tracks.path` |
|
||||
| `ss:plitem:<playlist_items.id>` | playable; `playlist_items.path`, **which may be NULL** |
|
||||
|
||||
A playlist row of kind `stream` or `unresolved` has no file and is listed
|
||||
and marked playable anyway, because the phone lists it: the round that plays
|
||||
these owes them `STATE_ERROR` and "open SoulStream on your phone" rather
|
||||
than a hole in the list. Catalog and Soulseek content cannot play from cold
|
||||
at all — the resolver is 872 lines of shared JS and Soulseek needs a live
|
||||
session — and that is the same answer.
|
||||
|
||||
**Every failure is an empty but valid tree.** No config, no database, a
|
||||
storage grant revoked since the last launch, a corrupt page: each of them
|
||||
answers an empty list with the reason in the envelope's `note`. `ss:root` is
|
||||
answered without opening anything at all, so the four tabs are there on a
|
||||
phone that has never had a library. A head unit that spins forever is worse
|
||||
than one that says the library is empty, and a driver cannot dismiss a
|
||||
dialog.
|
||||
|
||||
**Covers go out as `content://`, never as bytes.** Auto supports that scheme
|
||||
and `android.resource://` and no other, and `MediaLibraryServiceLegacyStub`
|
||||
will decode `MediaMetadata.artworkData` into a Bitmap for every item it
|
||||
converts — a few hundred of those is over Binder's 1 MB limit and the whole
|
||||
browse fails. So `auto.rs` picks the file (the warmed 360 px thumbnail from
|
||||
`library_art_prewarm`'s cache when it is there, the master otherwise) and
|
||||
`LibraryBridge` mints the URI through the app's FileProvider. That provider
|
||||
needed new roots: see `src-tauri/android/res/xml/file_paths.xml`, and note
|
||||
that `files-path` is the wrong tag — it maps `getFilesDir()`, and the
|
||||
library lives one level up under `getDataDir()`.
|
||||
|
||||
**And media3 grants nothing.** There is no `grantUriPermission` anywhere in
|
||||
`MediaSessionImpl`, `MediaSessionLegacyStub`, `MediaLibraryServiceLegacyStub`
|
||||
or `LegacyConversions` at 1.10.1, and the app's provider is
|
||||
`android:exported="false"` — so `LibraryBridge` grants each URI to the
|
||||
browser's own package by hand. Forget that line and the car browses
|
||||
perfectly and shows a grid of grey squares, with nothing in any log.
|
||||
|
||||
The phone walk's six `auto-*` checks run **with the app force-stopped**,
|
||||
which is the entire claim, and they read `LibraryBridge`'s own
|
||||
`auto node=… n=… … note=…` line out of logcat — the only place on a device
|
||||
with no screen where any of this can be read back.
|
||||
|
||||
Reference in new issue
Block a user