Add audio stream managing functionality to AudioRepository.
Bug: 318349141
Flag: aconfig new_volume_panel DISABLED
Test: atest AudioRepositoryTest
Change-Id: Ib244946dcd60fa4eb2a2b2b3afdd100a43e79df8
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index ce3a7ba..f303ab5 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -40,6 +40,8 @@
"android.test.runner",
"telephony-common",
"android.test.base",
+ "android.test.mock",
+ "truth",
],
platform_apis: true,
@@ -49,16 +51,23 @@
"androidx.test.core",
"androidx.test.rules",
"androidx.test.espresso.core",
+ "androidx.test.ext.junit",
"flag-junit",
- "mockito-target-minus-junit4",
+ "kotlinx_coroutines_test",
+ "mockito-target-extended-minus-junit4",
"platform-test-annotations",
"truth",
"SettingsLibDeviceStateRotationLock",
"SettingsLibSettingsSpinner",
"SettingsLibUsageProgressBarPreference",
"settingslib_media_flags_lib",
- "kotlinx_coroutines_test",
],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
dxflags: ["--multi-dex"],
+ manifest: "AndroidManifest.xml",
}
diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml
index 32048ca..9fb1c1f 100644
--- a/packages/SettingsLib/tests/integ/AndroidManifest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml
@@ -25,7 +25,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <application>
+ <application android:debuggable="true" android:testOnly="true">
<uses-library android:name="android.test.runner" />
<activity android:name=".drawer.SettingsDrawerActivityTest$TestActivity"/>
diff --git a/packages/SettingsLib/tests/integ/AndroidTest.xml b/packages/SettingsLib/tests/integ/AndroidTest.xml
index d0aee88..9de6019 100644
--- a/packages/SettingsLib/tests/integ/AndroidTest.xml
+++ b/packages/SettingsLib/tests/integ/AndroidTest.xml
@@ -16,6 +16,7 @@
<configuration description="Runs Tests for SettingsLib.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="SettingsLibTests.apk" />
+ <option name="install-arg" value="-t" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
new file mode 100644
index 0000000..7b70c64
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -0,0 +1,281 @@
+/*
+ * 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.volume.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+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.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@Suppress("UnspecifiedRegisterReceiverFlag")
+@RunWith(AndroidJUnit4::class)
+class AudioRepositoryTest {
+
+ @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ @Captor
+ private lateinit var modeListenerCaptor: ArgumentCaptor<AudioManager.OnModeChangedListener>
+ @Captor
+ private lateinit var communicationDeviceListenerCaptor:
+ ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var audioManager: AudioManager
+ @Mock private lateinit var communicationDevice: AudioDeviceInfo
+
+ private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
+ private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
+ private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
+ private val testScope = TestScope()
+
+ private lateinit var underTest: AudioRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(audioManager.mode).thenReturn(AudioManager.MODE_RINGTONE)
+ `when`(audioManager.communicationDevice).thenReturn(communicationDevice)
+ `when`(audioManager.getStreamMinVolume(anyInt())).thenReturn(MIN_VOLUME)
+ `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
+ `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+ `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
+ volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
+ triggerIntent(AudioManager.ACTION_VOLUME_CHANGED)
+ }
+ `when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
+ isMuteByStream[it.arguments[0] as Int] = it.arguments[2] == AudioManager.ADJUST_MUTE
+ triggerIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
+ }
+ `when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
+ volumeByStream.getOrDefault(it.arguments[0] as Int, 0)
+ }
+ `when`(audioManager.isStreamAffectedByRingerMode(anyInt())).thenAnswer {
+ isAffectedByRingerModeByStream.getOrDefault(it.arguments[0] as Int, false)
+ }
+ `when`(audioManager.isStreamMute(anyInt())).thenAnswer {
+ isMuteByStream.getOrDefault(it.arguments[0] as Int, false)
+ }
+
+ underTest =
+ AudioRepositoryImpl(
+ context,
+ audioManager,
+ testScope.testScheduler,
+ testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun audioModeChanges_repositoryEmits() {
+ testScope.runTest {
+ val modes = mutableListOf<Int>()
+ underTest.mode.onEach { modes.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerModeChange(AudioManager.MODE_IN_CALL)
+ runCurrent()
+
+ assertThat(modes).containsExactly(AudioManager.MODE_RINGTONE, AudioManager.MODE_IN_CALL)
+ }
+ }
+
+ @Test
+ fun ringerModeChanges_repositoryEmits() {
+ testScope.runTest {
+ val modes = mutableListOf<RingerMode>()
+ underTest.ringerMode.onEach { modes.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
+ triggerIntent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)
+ runCurrent()
+
+ assertThat(modes)
+ .containsExactly(
+ RingerMode(AudioManager.RINGER_MODE_NORMAL),
+ RingerMode(AudioManager.RINGER_MODE_SILENT),
+ )
+ }
+ }
+
+ @Test
+ fun communicationDeviceChanges_repositoryEmits() {
+ testScope.runTest {
+ var device: AudioDeviceInfo? = null
+ underTest.communicationDevice.onEach { device = it }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerConnectedDeviceChange(communicationDevice)
+ runCurrent()
+
+ assertThat(device).isSameInstanceAs(communicationDevice)
+ }
+ }
+
+ @Test
+ fun adjustingVolume_changesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setVolume(audioStream, 50)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 50,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun adjustingVolume_currentModeIsUpToDate() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setVolume(audioStream, 50)
+ runCurrent()
+
+ assertThat(underTest.getCurrentAudioStream(audioStream)).isEqualTo(streamModel)
+ }
+ }
+
+ @Test
+ fun muteStream_mutesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setMuted(audioStream, true)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = true,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun unmuteStream_unmutesTheStream() {
+ testScope.runTest {
+ val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
+ isMuteByStream[audioStream.value] = true
+ var streamModel: AudioStreamModel? = null
+ underTest
+ .getAudioStream(audioStream)
+ .onEach { streamModel = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ underTest.setMuted(audioStream, false)
+ runCurrent()
+
+ assertThat(streamModel)
+ .isEqualTo(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = MIN_VOLUME,
+ maxVolume = MAX_VOLUME,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+ }
+
+ private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
+ verify(audioManager)
+ .addOnCommunicationDeviceChangedListener(
+ any(),
+ communicationDeviceListenerCaptor.capture(),
+ )
+ communicationDeviceListenerCaptor.value.onCommunicationDeviceChanged(communicationDevice)
+ }
+
+ private fun triggerModeChange(mode: Int) {
+ verify(audioManager).addOnModeChangedListener(any(), modeListenerCaptor.capture())
+ modeListenerCaptor.value.onModeChanged(mode)
+ }
+
+ private fun triggerIntent(action: String) {
+ verify(context).registerReceiver(receiverCaptor.capture(), any())
+ receiverCaptor.value.onReceive(context, Intent(action))
+ }
+
+ private companion object {
+ const val MIN_VOLUME = 0
+ const val MAX_VOLUME = 100
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
index 686362f..dddf8e82 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
@@ -16,9 +16,15 @@
package com.android.settingslib.volume.data.repository
+import android.media.AudioDeviceInfo
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
class FakeAudioRepository : AudioRepository {
@@ -26,7 +32,59 @@
override val mode: StateFlow<Int>
get() = mutableMode.asStateFlow()
+ private val mutableRingerMode = MutableStateFlow(RingerMode(0))
+ override val ringerMode: StateFlow<RingerMode>
+ get() = mutableRingerMode.asStateFlow()
+
+ private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null)
+ override val communicationDevice: StateFlow<AudioDeviceInfo?>
+ get() = mutableCommunicationDevice.asStateFlow()
+
+ private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
+
+ private fun getAudioStreamModelState(
+ audioStream: AudioStream
+ ): MutableStateFlow<AudioStreamModel> =
+ models.getOrPut(audioStream) {
+ MutableStateFlow(
+ AudioStreamModel(
+ audioStream = audioStream,
+ volume = 0,
+ minVolume = 0,
+ maxVolume = 0,
+ isAffectedByRingerMode = false,
+ isMuted = false,
+ )
+ )
+ }
+
+ override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> =
+ getAudioStreamModelState(audioStream).asStateFlow()
+
+ override suspend fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel =
+ getAudioStreamModelState(audioStream).value
+
+ override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
+ getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
+ }
+
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
+ getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
+ }
+
fun setMode(newMode: Int) {
mutableMode.value = newMode
}
+
+ fun setRingerMode(newRingerMode: RingerMode) {
+ mutableRingerMode.value = newRingerMode
+ }
+
+ fun setCommunicationDevice(device: AudioDeviceInfo?) {
+ mutableCommunicationDevice.value = device
+ }
+
+ fun setAudioStreamModel(model: AudioStreamModel) {
+ getAudioStreamModelState(model.audioStream).update { model }
+ }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
index 3bc1edc..4dbf865 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
@@ -17,8 +17,9 @@
package com.android.settingslib.volume.domain.interactor
import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.android.settingslib.BaseTest
import com.android.settingslib.volume.data.repository.FakeAudioRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,7 +34,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
-class AudioModeInteractorTest {
+class AudioModeInteractorTest : BaseTest() {
private val testScope = TestScope()
private val fakeAudioRepository = FakeAudioRepository()