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()