diff options
5 files changed, 377 insertions, 0 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExt.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExt.kt new file mode 100644 index 000000000000..02d684d7d0ce --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExt.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.media.data.repository + +import android.media.AudioManager +import android.media.IVolumeController +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.launch + +/** Returns [AudioManager.setVolumeController] events as a [Flow] */ +fun AudioManager.volumeControllerEvents(): Flow<VolumeControllerEvent> = + callbackFlow { + volumeController = + object : IVolumeController.Stub() { + override fun displaySafeVolumeWarning(flags: Int) { + launch { send(VolumeControllerEvent.DisplaySafeVolumeWarning(flags)) } + } + + override fun volumeChanged(streamType: Int, flags: Int) { + launch { send(VolumeControllerEvent.VolumeChanged(streamType, flags)) } + } + + override fun masterMuteChanged(flags: Int) { + launch { send(VolumeControllerEvent.MasterMuteChanged(flags)) } + } + + override fun setLayoutDirection(layoutDirection: Int) { + launch { send(VolumeControllerEvent.SetLayoutDirection(layoutDirection)) } + } + + override fun dismiss() { + launch { send(VolumeControllerEvent.Dismiss) } + } + + override fun setA11yMode(mode: Int) { + launch { send(VolumeControllerEvent.SetA11yMode(mode)) } + } + + override fun displayCsdWarning( + csdWarning: Int, + displayDurationMs: Int, + ) { + launch { + send( + VolumeControllerEvent.DisplayCsdWarning( + csdWarning, + displayDurationMs, + ) + ) + } + } + } + awaitClose { volumeController = null } + } + .buffer() + +/** Models events received via [IVolumeController] */ +sealed interface VolumeControllerEvent { + + /** @see [IVolumeController.displaySafeVolumeWarning] */ + data class DisplaySafeVolumeWarning(val flags: Int) : VolumeControllerEvent + + /** @see [IVolumeController.volumeChanged] */ + data class VolumeChanged(val streamType: Int, val flags: Int) : VolumeControllerEvent + + /** @see [IVolumeController.masterMuteChanged] */ + data class MasterMuteChanged(val flags: Int) : VolumeControllerEvent + + /** @see [IVolumeController.setLayoutDirection] */ + data class SetLayoutDirection(val layoutDirection: Int) : VolumeControllerEvent + + /** @see [IVolumeController.setA11yMode] */ + data class SetA11yMode(val mode: Int) : VolumeControllerEvent + + /** @see [IVolumeController.displayCsdWarning] */ + data class DisplayCsdWarning( + val csdWarning: Int, + val displayDurationMs: Int, + ) : VolumeControllerEvent + + /** @see [IVolumeController.dismiss] */ + data object Dismiss : VolumeControllerEvent +}
\ No newline at end of file diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExtTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExtTest.kt new file mode 100644 index 000000000000..83b612df8dc9 --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExtTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.media.data.repository + +import android.media.AudioManager +import android.media.IVolumeController +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class AudioManagerVolumeControllerExtTest { + + private val testScope = TestScope() + + @Captor private lateinit var volumeControllerCaptor: ArgumentCaptor<IVolumeController> + @Mock private lateinit var audioManager: AudioManager + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun displaySafeVolumeWarning_emitsEvent() = + testEvent(VolumeControllerEvent.DisplaySafeVolumeWarning(1)) { displaySafeVolumeWarning(1) } + + @Test + fun volumeChanged_emitsEvent() = + testEvent(VolumeControllerEvent.VolumeChanged(1, 2)) { volumeChanged(1, 2) } + + @Test + fun masterMuteChanged_emitsEvent() = + testEvent(VolumeControllerEvent.MasterMuteChanged(1)) { masterMuteChanged(1) } + + @Test + fun setLayoutDirection_emitsEvent() = + testEvent(VolumeControllerEvent.SetLayoutDirection(1)) { setLayoutDirection(1) } + + @Test + fun setA11yMode_emitsEvent() = + testEvent(VolumeControllerEvent.SetA11yMode(1)) { setA11yMode(1) } + + @Test + fun displayCsdWarning_emitsEvent() = + testEvent(VolumeControllerEvent.DisplayCsdWarning(1, 2)) { displayCsdWarning(1, 2) } + + @Test fun dismiss_emitsEvent() = testEvent(VolumeControllerEvent.Dismiss) { dismiss() } + + private fun testEvent( + expectedEvent: VolumeControllerEvent, + emit: IVolumeController.() -> Unit, + ) = + testScope.runTest { + var event: VolumeControllerEvent? = null + audioManager.volumeControllerEvents().onEach { event = it }.launchIn(backgroundScope) + runCurrent() + verify(audioManager).volumeController = volumeControllerCaptor.capture() + + volumeControllerCaptor.value.emit() + runCurrent() + + assertThat(event).isEqualTo(expectedEvent) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt new file mode 100644 index 000000000000..68591910031d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume + +import android.media.IVolumeController +import com.android.settingslib.media.data.repository.VolumeControllerEvent +import com.android.systemui.dagger.qualifiers.Application +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch + +/** + * This class is a bridge between + * [com.android.settingslib.volume.data.repository.AudioRepository.volumeControllerEvents] and the + * old code that uses [IVolumeController] interface directly. + */ +class VolumeControllerCollector +@Inject +constructor(@Application private val coroutineScope: CoroutineScope) { + + /** Collects [Flow] of [VolumeControllerEvent] into [IVolumeController]. */ + fun collectToController( + eventsFlow: Flow<VolumeControllerEvent>, + controller: IVolumeController + ) = + coroutineScope.launch { + eventsFlow.collect { event -> + when (event) { + is VolumeControllerEvent.VolumeChanged -> + controller.volumeChanged(event.streamType, event.flags) + VolumeControllerEvent.Dismiss -> controller.dismiss() + is VolumeControllerEvent.DisplayCsdWarning -> + controller.displayCsdWarning(event.csdWarning, event.displayDurationMs) + is VolumeControllerEvent.DisplaySafeVolumeWarning -> + controller.displaySafeVolumeWarning(event.flags) + is VolumeControllerEvent.MasterMuteChanged -> + controller.masterMuteChanged(event.flags) + is VolumeControllerEvent.SetA11yMode -> controller.setA11yMode(event.mode) + is VolumeControllerEvent.SetLayoutDirection -> + controller.setLayoutDirection(event.layoutDirection) + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt new file mode 100644 index 000000000000..dd78e4a1fdaa --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume + +import android.media.IVolumeController +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.media.data.repository.VolumeControllerEvent +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +@SmallTest +class VolumeControllerCollectorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val eventsFlow = MutableStateFlow<VolumeControllerEvent?>(null) + private val underTest = VolumeControllerCollector(kosmos.applicationCoroutineScope) + + private val volumeController = mock<IVolumeController> {} + + @Test + fun volumeControllerEvent_volumeChanged_callsMethod() = + testEvent(VolumeControllerEvent.VolumeChanged(3, 0)) { + verify(volumeController) { 1 * { volumeController.volumeChanged(eq(3), eq(0)) } } + } + + @Test + fun volumeControllerEvent_dismiss_callsMethod() = + testEvent(VolumeControllerEvent.Dismiss) { + verify(volumeController) { 1 * { volumeController.dismiss() } } + } + + @Test + fun volumeControllerEvent_displayCsdWarning_callsMethod() = + testEvent(VolumeControllerEvent.DisplayCsdWarning(0, 1)) { + verify(volumeController) { 1 * { volumeController.displayCsdWarning(eq(0), eq(1)) } } + } + + @Test + fun volumeControllerEvent_displaySafeVolumeWarning_callsMethod() = + testEvent(VolumeControllerEvent.DisplaySafeVolumeWarning(1)) { + verify(volumeController) { 1 * { volumeController.displaySafeVolumeWarning(eq(1)) } } + } + + @Test + fun volumeControllerEvent_masterMuteChanged_callsMethod() = + testEvent(VolumeControllerEvent.MasterMuteChanged(1)) { + verify(volumeController) { 1 * { volumeController.masterMuteChanged(1) } } + } + + @Test + fun volumeControllerEvent_setA11yMode_callsMethod() = + testEvent(VolumeControllerEvent.SetA11yMode(1)) { + verify(volumeController) { 1 * { volumeController.setA11yMode(1) } } + } + + @Test + fun volumeControllerEvent_SetLayoutDirection_callsMethod() = + testEvent(VolumeControllerEvent.SetLayoutDirection(1)) { + verify(volumeController) { 1 * { volumeController.setLayoutDirection(eq(1)) } } + } + + private fun testEvent(event: VolumeControllerEvent, verify: () -> Unit) = + kosmos.testScope.runTest { + underTest.collectToController(eventsFlow.filterNotNull(), volumeController) + + eventsFlow.value = event + runCurrent() + + verify() + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt new file mode 100644 index 000000000000..d60f14cab28f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope + +val Kosmos.volumeControllerCollector by + Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) } |