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,61 @@
|
||||
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.
|
||||
Reference in new issue
Block a user