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.
62 lines
4.2 KiB
Plaintext
62 lines
4.2 KiB
Plaintext
android: the lock-screen card, moved to media3 before anything is built on it
|
|
|
|
The card Kyle sees every day was a hand-built MediaStyle notification over a
|
|
MediaSessionCompat, on `androidx.media`, deprecated since May. Android Auto
|
|
needs a media3 session to browse, so the choice was to write the new thing
|
|
against a deprecated library or to move the old thing first; this moves the
|
|
old thing and adds no Auto. `MediaService` is a `MediaSessionService` and a
|
|
new `SoulStreamPlayer` is the `SimpleBasePlayer` media3 asks for when the
|
|
engine lives somewhere else — rodio, in Rust, behind a WebView round trip —
|
|
so `getState()` is a pure read of the last `NowPlayingArgs` off the bridge,
|
|
queue window and all, and every button routes back out through `MediaHub.act`.
|
|
|
|
Three corrections to the plan, each the kind that only shows up on a phone.
|
|
media3 **1.10.1**, not 1.11.0, whose POM drags kotlin-stdlib 2.2.10 into a
|
|
project that compiles with Kotlin 1.9.25. The audio-focus and becoming-noisy
|
|
blocks STAY hand-rolled: `setAudioAttributes(…, handleAudioFocus)` is an
|
|
ExoPlayer convenience that lands in a `@ForOverride` method which throws, and
|
|
deleting them would have lost "another app takes the output" and "headphones
|
|
out" with nothing failing anywhere. And the service is BOUND, never started —
|
|
`MediaSessionService.onStartCommand` returns `START_STICKY` without promoting
|
|
for any intent that is not media3's own, so a `startForegroundService` would
|
|
arm the ten-second kill timer and satisfy nothing, which is the 2026-09-13 bug
|
|
in a new coat. Four more things are load-bearing and silent when they break: a
|
|
playlist that must never be empty while a track exists, `COMMAND_GET_TIMELINE`
|
|
with `COMMAND_GET_CURRENT_MEDIA_ITEM`, `STATE_READY` while paused, and both
|
|
spellings of next and previous. Each ends in a card that is simply never
|
|
posted, with nothing in any log. The window carries `queueBase` now, for a
|
|
stable media-item uid as it slides, and `repeat`, whose numbering is swapped
|
|
between the app and media3, so that what a controller is told about repeat is
|
|
the truth and media3's own view of the end of the queue agrees with the
|
|
`hasNext` the next button is advertised on.
|
|
|
|
The 2026-09-13 bug has a second door, and `onStartCommand` is what shuts it.
|
|
media3 hands the framework a media-button `PendingIntent` built with
|
|
`PendingIntent.getForegroundService`, unconditionally, because the receiver the
|
|
legacy stub resolves is now this service — there being no `MediaButtonReceiver`
|
|
left for it to find. So the SYSTEM starts this service in the foreground on
|
|
every media KEY, while media3 promotes only for a session it calls
|
|
user-engaged. A key into a process built for that key, a key on a card paused
|
|
past the ten-minute tail that does not start playback, any key with the bar
|
|
cleared: none of them qualify, and each would take the app down ten seconds
|
|
later. (The card's own buttons are milder — only play/pause, and only while
|
|
paused, is a foreground start.) So `onStartCommand` promotes first, the way
|
|
this service always did — through media3's own `onUpdateNotification` when
|
|
there is a card to post, and otherwise with a plain notification of its own,
|
|
posted and withdrawn in the same breath. `Service`'s
|
|
`startForeground` is final, so the split is by what media3 can be asked to do,
|
|
not by watching it.
|
|
|
|
The walk gains `one-card` and `one-session` — the only checks that tell a
|
|
landed migration from a doubled one — plus an advisory `media-plumbing` line;
|
|
the logged window grew `base` and `rep`, which are the only readback either
|
|
field has anywhere on the device and which `queue-window` now asserts; and the
|
|
paused-restore wait goes to 25 s, which ANDROID.md had already claimed in one
|
|
place and contradicted in another. ANDROID.md also says plainly what the walk
|
|
cannot prove and Kyle has to try himself: headphones out, another app taking
|
|
focus, the card's actual look, and a Bluetooth headset's own buttons. The
|
|
Kotlin is unbuilt — no Kotlin compiler here — and was read against javap of
|
|
media3 1.10.1 and the real sources of `SimpleBasePlayer`, `MediaSessionService`,
|
|
`MediaNotificationManager` and `DefaultMediaNotificationProvider`; the manifest
|
|
and resources compile and link under aapt2, and the Rust type-checks.
|