inject AudioFocusRequest.Builder and fix lint

Signed-off-by: alperozturk <alper_ozturk@proton.me>
This commit is contained in:
alperozturk 2025-06-12 14:24:00 +02:00
parent 2b44bbea8f
commit b7e96ad0a7
No known key found for this signature in database
GPG Key ID: 4E577DC593B59BDF
2 changed files with 38 additions and 61 deletions

View File

@ -8,7 +8,6 @@ package com.nextcloud.client.media
import android.media.AudioFocusRequest import android.media.AudioFocusRequest
import android.media.AudioManager import android.media.AudioManager
import android.os.Build
/** /**
* Wrapper around audio manager exposing simplified audio focus API and * Wrapper around audio manager exposing simplified audio focus API and
@ -19,45 +18,29 @@ import android.os.Build
*/ */
internal class AudioFocusManager( internal class AudioFocusManager(
private val audioManger: AudioManager, private val audioManger: AudioManager,
private val onFocusChange: (AudioFocus) -> Unit private val onFocusChange: (AudioFocus) -> Unit,
requestBuilder: AudioFocusRequest.Builder = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
) { ) {
private val focusListener = AudioManager.OnAudioFocusChangeListener { focusChange ->
private val focusListener = object : AudioManager.OnAudioFocusChangeListener { val focus = when (focusChange) {
override fun onAudioFocusChange(focusChange: Int) { AudioManager.AUDIOFOCUS_GAIN,
val focus = when (focusChange) { AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
AudioManager.AUDIOFOCUS_GAIN -> AudioFocus.FOCUS AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK -> AudioFocus.FOCUS
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT -> AudioFocus.FOCUS AudioManager.AUDIOFOCUS_LOSS,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK -> AudioFocus.FOCUS AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> AudioFocus.LOST
AudioManager.AUDIOFOCUS_LOSS -> AudioFocus.LOST AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> AudioFocus.DUCK
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> AudioFocus.LOST else -> null
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> AudioFocus.DUCK
else -> null
}
focus?.let { onFocusChange(it) }
}
}
private var focusRequest: AudioFocusRequest? = null
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
setWillPauseWhenDucked(true)
setOnAudioFocusChangeListener(focusListener)
}.build()
} }
focus?.let { onFocusChange(it) }
} }
/** private val focusRequest = requestBuilder
* Request audio focus. Focus is reported via callback. .setWillPauseWhenDucked(true)
* If focus cannot be gained, lost of focus is reported. .setOnAudioFocusChangeListener(focusListener)
*/ .build()
fun requestFocus() { fun requestFocus() {
val requestResult = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val requestResult = audioManger.requestAudioFocus(focusRequest)
focusRequest?.let { audioManger.requestAudioFocus(it) }
} else {
audioManger.requestAudioFocus(focusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
}
if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
focusListener.onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN) focusListener.onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN)
} else { } else {
@ -65,17 +48,8 @@ internal class AudioFocusManager(
} }
} }
/**
* Release audio focus. Loss of focus is reported via callback.
*/
fun releaseFocus() { fun releaseFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { audioManger.abandonAudioFocusRequest(focusRequest)
focusRequest?.let {
audioManger.abandonAudioFocusRequest(it)
} ?: AudioManager.AUDIOFOCUS_REQUEST_FAILED
} else {
audioManger.abandonAudioFocus(focusListener)
}
focusListener.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS) focusListener.onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS)
} }
} }

View File

@ -10,50 +10,53 @@ import android.media.AudioFocusRequest
import android.media.AudioManager import android.media.AudioManager
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.ArgumentMatcher
import org.mockito.kotlin.any import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.mockito.kotlin.verify import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever import org.mockito.kotlin.whenever
class AudioFocusManagerTest { class AudioFocusManagerTest {
private val audioManager = mock<AudioManager>() private val audioManager: AudioManager = mock()
private val callback = mock<(AudioFocus) -> Unit>() private val callback: (AudioFocus) -> Unit = mock()
private lateinit var audioFocusManager: AudioFocusManager private lateinit var audioFocusManager: AudioFocusManager
val audioRequestMatcher = object : ArgumentMatcher<AudioFocusRequest> { private val builder: AudioFocusRequest.Builder = mock()
override fun matches(argument: AudioFocusRequest?): Boolean = true private val focusRequest: AudioFocusRequest = mock()
}
@Before @Before
fun setUp() { fun setUp() {
audioFocusManager = AudioFocusManager(audioManager, callback) // Chain mock methods for the builder
whenever(audioManager.requestAudioFocus(any(), any(), any())) whenever(builder.setWillPauseWhenDucked(true)).thenReturn(builder)
whenever(builder.setOnAudioFocusChangeListener(any())).thenReturn(builder)
whenever(builder.build()).thenReturn(focusRequest)
audioFocusManager = AudioFocusManager(audioManager, callback, builder)
whenever(audioManager.requestAudioFocus(focusRequest))
.thenReturn(AudioManager.AUDIOFOCUS_REQUEST_GRANTED) .thenReturn(AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
whenever(audioManager.abandonAudioFocusRequest(argThat(audioRequestMatcher)))
.thenReturn(AudioManager.AUDIOFOCUS_REQUEST_GRANTED) whenever(audioManager.abandonAudioFocusRequest(focusRequest))
whenever(audioManager.abandonAudioFocusRequest(any()))
.thenReturn(AudioManager.AUDIOFOCUS_REQUEST_GRANTED) .thenReturn(AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
} }
@Test @Test
fun `acquiring focus triggers callback immediately`() { fun `requestFocus should invoke FOCUS callback when granted`() {
audioFocusManager.requestFocus() audioFocusManager.requestFocus()
verify(callback).invoke(AudioFocus.FOCUS) verify(callback).invoke(AudioFocus.FOCUS)
} }
@Test @Test
fun `failing to acquire focus triggers callback immediately`() { fun `requestFocus should invoke LOST callback when denied`() {
whenever(audioManager.requestAudioFocus(any(), any(), any())) whenever(audioManager.requestAudioFocus(focusRequest))
.thenReturn(AudioManager.AUDIOFOCUS_REQUEST_FAILED) .thenReturn(AudioManager.AUDIOFOCUS_REQUEST_FAILED)
audioFocusManager.requestFocus() audioFocusManager.requestFocus()
verify(callback).invoke(AudioFocus.LOST) verify(callback).invoke(AudioFocus.LOST)
} }
@Test @Test
fun `releasing focus triggers callback immediately`() { fun `releaseFocus should invoke LOST callback`() {
audioFocusManager.releaseFocus() audioFocusManager.releaseFocus()
verify(callback).invoke(AudioFocus.LOST) verify(callback).invoke(AudioFocus.LOST)
} }