finish recording gracefully when audio focus is lost

if something else grabs focus mid-recording (phone call, etc), stop
the recording and keep the partial result in preview state instead
of silently recording garbage

Signed-off-by: vmfunc <celeste@linux.com>
This commit is contained in:
vmfunc
2026-02-12 18:30:34 +01:00
parent 7bd157f032
commit bddc5a63f4
2 changed files with 30 additions and 1 deletions

View File

@@ -249,7 +249,11 @@ class DefaultVoiceMessageComposerPresenter(
private fun CoroutineScope.startRecording() = launch {
try {
audioFocus.requestAudioFocus(AudioFocusRequester.RecordVoiceMessage) {}
audioFocus.requestAudioFocus(AudioFocusRequester.RecordVoiceMessage) {
// something else grabbed focus (phone call, etc) - finish gracefully
// so the user keeps their partial recording
sessionCoroutineScope.finishRecording()
}
voiceRecorder.startRecord()
} catch (e: SecurityException) {
audioFocus.releaseAudioFocus()

View File

@@ -199,6 +199,30 @@ class DefaultVoiceMessageComposerPresenterTest {
}
}
@Test
fun `present - audio focus loss during recording finishes gracefully`() = runTest {
var onFocusLost: (() -> Unit)? = null
val testAudioFocus = FakeAudioFocus(
requestAudioFocusResult = { _, callback -> onFocusLost = callback },
releaseAudioFocusResult = { },
)
val presenter = createDefaultVoiceMessageComposerPresenter(audioFocus = testAudioFocus)
presenter.test {
awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start))
awaitItem()
// simulate focus loss (phone call, etc)
onFocusLost?.invoke()
advanceUntilIdle()
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(aPreviewState())
voiceRecorder.assertCalls(started = 1, stopped = 1)
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - abort recording`() = runTest {
val presenter = createDefaultVoiceMessageComposerPresenter()
@@ -687,6 +711,7 @@ class DefaultVoiceMessageComposerPresenterTest {
private fun TestScope.createDefaultVoiceMessageComposerPresenter(
permissionsPresenter: PermissionsPresenter = createFakePermissionsPresenter(),
voiceRecorder: VoiceRecorder = this@DefaultVoiceMessageComposerPresenterTest.voiceRecorder,
audioFocus: AudioFocus = this@DefaultVoiceMessageComposerPresenterTest.audioFocus,
): DefaultVoiceMessageComposerPresenter {
return DefaultVoiceMessageComposerPresenter(
sessionCoroutineScope = backgroundScope,