Verify native notification artwork with real JPEG regression
This commit is contained in:
@@ -58,6 +58,38 @@ class CompanionAudioTest {
|
||||
} finally { CompanionAudioBridge.release(web); lifecycle.destroy(); web.destroy() }
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = [RecordingAudioMediaSession::class])
|
||||
fun jpegArtworkReachesNotificationAndMediaDescription() {
|
||||
// Real 16x16 JPEG generated by Chromium canvas, independent of Android bitmap shadows.
|
||||
val artwork = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAn/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJVAA//Z"
|
||||
assertNotNull(decodeArtwork(artwork))
|
||||
val app = RuntimeEnvironment.getApplication()
|
||||
val web = android.webkit.WebView(app); web.loadUrl("https://node.test/cloud")
|
||||
val payload = state().put("session", "32345678-1234-1234-1234-123456789abc").put("artwork", artwork)
|
||||
CompanionAudioBridge.receive(web, payload.toString(), "https://node.test", true, setOf("https://node.test")) {}
|
||||
val lifecycle = Robolectric.buildService(CompanionAudioService::class.java).create()
|
||||
try {
|
||||
lifecycle.get().onStartCommand(null, 0, 1)
|
||||
val notification = Shadows.shadowOf(lifecycle.get()).lastForegroundNotification
|
||||
assertNotNull(notification.getLargeIcon())
|
||||
// Robolectric's MediaController does not read MediaSession metadata;
|
||||
// capture the actual service's setMetadata call instead.
|
||||
val metadata = RecordingAudioMediaSession.metadata!!
|
||||
assertNotNull(metadata.getBitmap(android.media.MediaMetadata.METADATA_KEY_ALBUM_ART))
|
||||
assertNotNull(metadata.description.iconBitmap)
|
||||
// Position-only refresh must retain the same thumbnail.
|
||||
CompanionAudioBridge.receive(web, state().put("session", payload.getString("session")).put("sequence", 2).toString(),
|
||||
"https://node.test", true, setOf("https://node.test")) {}
|
||||
assertNotNull(Shadows.shadowOf(lifecycle.get()).lastForegroundNotification.getLargeIcon())
|
||||
// A following song without art must not keep the previous cover.
|
||||
CompanionAudioBridge.receive(web, state().put("session", payload.getString("session")).put("sequence", 3).put("artwork", "").toString(),
|
||||
"https://node.test", true, setOf("https://node.test")) {}
|
||||
assertNull(Shadows.shadowOf(lifecycle.get()).lastForegroundNotification.getLargeIcon())
|
||||
assertNull(RecordingAudioMediaSession.metadata!!.description.iconBitmap)
|
||||
} finally { CompanionAudioBridge.release(web); lifecycle.destroy(); web.destroy() }
|
||||
}
|
||||
|
||||
@Test fun diagnosticReportExcludesPrivateMessagesAndRecordsRejectionStage() {
|
||||
val app = RuntimeEnvironment.getApplication()
|
||||
val web = android.webkit.WebView(app)
|
||||
@@ -116,3 +148,10 @@ class CompanionAudioTest {
|
||||
} finally { lifecycle.destroy() }
|
||||
}
|
||||
}
|
||||
|
||||
@org.robolectric.annotation.Implements(android.media.session.MediaSession::class)
|
||||
class RecordingAudioMediaSession : org.robolectric.shadows.ShadowMediaSession() {
|
||||
companion object { var metadata: android.media.MediaMetadata? = null }
|
||||
@org.robolectric.annotation.Implementation
|
||||
fun setMetadata(value: android.media.MediaMetadata) { metadata = value }
|
||||
}
|
||||
|
||||
@@ -124,10 +124,27 @@ Evidence:
|
||||
never produced a thumbnail; corrected composable produced a real JPEG. Browser
|
||||
checks also verified no cross-origin cookie, no followed redirect, and playback
|
||||
remained active after the optional image error.
|
||||
- No native runtime or APK version change. A positive native JPEG/notification/
|
||||
metadata regression is prepared separately and awaits its coordinated test slot.
|
||||
- No native runtime or APK version change. The focused positive native JPEG
|
||||
regression passed on SDK 28 and 35 (2 tests, zero failures/errors/skips). It
|
||||
verifies the actual service places a real JPEG in the notification large icon
|
||||
and media-description bitmap, retains it on position-only updates, and clears
|
||||
it for an explicitly artless following song. Initial test compilation used
|
||||
unavailable Java desktop ImageIO APIs; replacing that test-only fixture with a
|
||||
fixed Chromium-generated JPEG resolved the harness issue. Production Kotlin
|
||||
remained unchanged/up-to-date. All owned test JVMs exited after success.
|
||||
- This identifies and fixes a real artwork boundary defect, not the operator's
|
||||
exact cover URL. Physical notification-artwork acceptance remains open until
|
||||
the qualified UI reaches the node and the operator verifies it.
|
||||
|
||||
Evidence folder: `~/.local/state/archipelago/release-qualification/companion-artwork-20261007/`.
|
||||
|
||||
## Actual app artwork access limitation
|
||||
|
||||
A separate read-only Chromium probe retained the managed node-demo-v4v iframe,
|
||||
but the actual app required Nostr login before initializing its media catalog.
|
||||
With signing and payments blocked, the probe timed out before obtaining a cover
|
||||
URL. No image access/CORS policy was bypassed, no credentials were forwarded,
|
||||
and no real app artwork was decoded in that probe. The successful browser A/B
|
||||
fixture and native JPEG test establish the corrected pipeline, not that the
|
||||
operator's particular cover is anonymously accessible. Physical artwork acceptance
|
||||
remains pending after the qualified UI deployment; APK 0.5.37 stays unchanged.
|
||||
|
||||
Reference in New Issue
Block a user