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

View File

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