ReviewOS

also looking at this

craft-native/craft

fix(native): interaction, audio, document and checkpoint gaps stranded after #401

#415
Merged glennmichael123 wants to merge codex/issues-388-400-344 into main
19 files +987 -50
packages/android/fixtures/native-navigation/NativeCapabilitiesTest.ktmodified+97-0
Changes to packages/android/fixtures/native-navigation/NativeCapabilitiesTest.kt
@@ -1,6 +1,9 @@
11package dev.craft.navigationtest
22
33import android.content.Intent
4import android.media.AudioManager
5import android.media.MediaPlayer
6import androidx.test.core.app.ActivityScenario
47import androidx.test.ext.junit.runners.AndroidJUnit4
58import androidx.test.platform.app.InstrumentationRegistry
69import java.net.ServerSocket
@@ -43,6 +46,100 @@ class NativeCapabilitiesTest {
4346
4447 private val instrumentation = InstrumentationRegistry.getInstrumentation()
4548
49 // A real local PCM file keeps these playback regressions independent of
50 // networking, codecs downloaded by the device, and the user's media.
51 private fun silentWav(): java.io.File {
52 val samples = 8000 * 2
53 val bytes = java.nio.ByteBuffer.allocate(44 + samples * 2).order(java.nio.ByteOrder.LITTLE_ENDIAN)
54 bytes.put("RIFF".toByteArray()).putInt(36 + samples * 2).put("WAVEfmt ".toByteArray())
55 bytes.putInt(16).putShort(1.toShort()).putShort(1.toShort()).putInt(8000).putInt(16000)
56 bytes.putShort(2.toShort()).putShort(16.toShort()).put("data".toByteArray()).putInt(samples * 2)
57 return java.io.File(instrumentation.targetContext.cacheDir, "sound-${UUID.randomUUID()}.wav").apply { writeBytes(bytes.array()) }
58 }
59
60 private fun sound(service: CraftNativeSounds): Any =
61 (CraftNativeSounds::class.java.getDeclaredField("sounds").apply { isAccessible = true }.get(service) as Map<*, *>)["tone"]!!
62
63 private fun field(sound: Any, name: String): Any? =
64 sound.javaClass.getDeclaredField(name).apply { isAccessible = true }.get(sound)
65
66 private fun focus(service: CraftNativeSounds, change: Int) {
67 CraftNativeSounds::class.java.getDeclaredMethod("onAudioFocusChange", Int::class.javaPrimitiveType)
68 .apply { isAccessible = true }.invoke(service, change)
69 }
70
71 @Test
72 fun replacingAPreparingSoundSettlesItAndItsOldCancellationCannotStopTheReplacement() {
73 val file = silentWav()
74 try {
75 ActivityScenario.launch(MainActivity::class.java).use { scenario ->
76 lateinit var service: CraftNativeSounds
77 val ready = CountDownLatch(1)
78 var replacement: CraftNativeCapabilityResult? = null
79 var cancelled = 0
80 try {
81 scenario.onActivity { activity ->
82 service = CraftNativeSounds(activity)
83 service.play("tone", file.toURI().toString(), JSONObject()) {
84 assertEquals("CANCELLED", it.code)
85 cancelled++
86 }
87 val oldCancel = service.cancellation("tone")
88 service.play("tone", file.toURI().toString(), JSONObject().put("loop", true)) {
89 replacement = it
90 ready.countDown()
91 }
92 assertEquals(1, cancelled)
93 oldCancel()
94 }
95 assertTrue("replacement did not prepare", ready.await(10, TimeUnit.SECONDS))
96 assertNull(replacement?.message, replacement?.code)
97 scenario.onActivity {
98 assertTrue((field(sound(service), "player") as MediaPlayer).isPlaying)
99 assertEquals(1, cancelled)
100 }
101 } finally { scenario.onActivity { service.close() } }
102 }
103 } finally { file.delete() }
104 }
105
106 @Test
107 fun explicitPauseSurvivesFocusGainAndVolumeChangesStayCurrent() {
108 val file = silentWav()
109 try {
110 ActivityScenario.launch(MainActivity::class.java).use { scenario ->
111 lateinit var service: CraftNativeSounds
112 val ready = CountDownLatch(1)
113 var result: CraftNativeCapabilityResult? = null
114 try {
115 scenario.onActivity { activity ->
116 service = CraftNativeSounds(activity)
117 service.play("tone", file.toURI().toString(), JSONObject().put("loop", true).put("volume", 0.8).put("fadeIn", 1000)) {
118 result = it
119 ready.countDown()
120 }
121 assertEquals(0f, field(sound(service), "volume"))
122 }
123 assertTrue("sound did not prepare", ready.await(10, TimeUnit.SECONDS))
124 assertNull(result?.message, result?.code)
125 scenario.onActivity {
126 assertTrue(service.setVolume("tone", 0.25, 0.0))
127 assertEquals(0.25f, field(sound(service), "volume"))
128 focus(service, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)
129 focus(service, AudioManager.AUDIOFOCUS_GAIN)
130 assertEquals(0.25f, field(sound(service), "volume"))
131 focus(service, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)
132 assertTrue(service.pause("tone"))
133 focus(service, AudioManager.AUDIOFOCUS_GAIN)
134 assertFalse((field(sound(service), "player") as MediaPlayer).isPlaying)
135 assertTrue(service.resume("tone"))
136 assertTrue((field(sound(service), "player") as MediaPlayer).isPlaying)
137 }
138 } finally { scenario.onActivity { service.close() } }
139 }
140 } finally { file.delete() }
141 }
142
46143 @Test
47144 fun rejectedSnapshotReplacementPreservesDiskCacheAndTheOtherIsolates() {
48145 val base = instrumentation.targetContext