diff --git a/app/src/main/java/com/nextcloud/client/media/AudioFocusManager.kt b/app/src/main/java/com/nextcloud/client/media/AudioFocusManager.kt index 3062a804df..358b39344b 100644 --- a/app/src/main/java/com/nextcloud/client/media/AudioFocusManager.kt +++ b/app/src/main/java/com/nextcloud/client/media/AudioFocusManager.kt @@ -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) } } diff --git a/app/src/test/java/com/nextcloud/client/media/AudioFocusManagerTest.kt b/app/src/test/java/com/nextcloud/client/media/AudioFocusManagerTest.kt index 6d205a5f17..f40e312d92 100644 --- a/app/src/test/java/com/nextcloud/client/media/AudioFocusManagerTest.kt +++ b/app/src/test/java/com/nextcloud/client/media/AudioFocusManagerTest.kt @@ -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() - 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 { - 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) }