summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Anton Potapov <apotapov@google.com> 2024-08-28 11:42:40 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-08-28 11:42:40 +0000
commit7fec56e0fd1378bcce5a0d237d96ee5da77e0727 (patch)
tree7e31f2a5784a57f105087e31490b99ecc1da4352
parent0995966daa09f30ab40362375d7a1f51b3d6624c (diff)
parent5cb6fe05b32cd0b1cea99779375db640d5ec2851 (diff)
Merge "Add listening to the IVolumeController for the volume changes in AudioRepository." into main
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExt.kt100
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/model/VolumeControllerEvent.kt47
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt123
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt27
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryVolumeControllerEventsTest.kt (renamed from packages/SettingsLib/tests/integ/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExtTest.kt)25
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeControllerAdapter.kt (renamed from packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt)22
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt)18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt156
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerAdapterKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt)5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt32
14 files changed, 447 insertions, 152 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
deleted file mode 100644
index 02d684d7d0ce..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExt.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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/src/com/android/settingslib/volume/data/model/VolumeControllerEvent.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/model/VolumeControllerEvent.kt
new file mode 100644
index 000000000000..0fe385b3d02a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/model/VolumeControllerEvent.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.model
+
+import android.media.IVolumeController
+
+/** 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
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 0e71116db6cc..3e2d8328f21e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -22,9 +22,12 @@ import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.media.AudioManager.AudioDeviceCategory
import android.media.AudioManager.OnCommunicationDeviceChangedListener
+import android.media.IVolumeController
import android.provider.Settings
+import android.util.Log
import androidx.concurrent.futures.DirectExecutor
import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.data.model.VolumeControllerEvent
import com.android.settingslib.volume.shared.AudioLogger
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.settingslib.volume.shared.model.AudioManagerEvent
@@ -36,10 +39,13 @@ import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
@@ -73,6 +79,11 @@ interface AudioRepository {
*/
val communicationDevice: StateFlow<AudioDeviceInfo?>
+ /** Events from [AudioManager.setVolumeController] */
+ val volumeControllerEvents: Flow<VolumeControllerEvent>
+
+ fun init()
+
/** State of the [AudioStream]. */
fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
@@ -90,8 +101,9 @@ interface AudioRepository {
suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
/** Gets audio device category. */
- @AudioDeviceCategory
- suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int
+ @AudioDeviceCategory suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int
+
+ suspend fun notifyVolumeControllerVisible(isVisible: Boolean)
}
class AudioRepositoryImpl(
@@ -101,8 +113,10 @@ class AudioRepositoryImpl(
private val backgroundCoroutineContext: CoroutineContext,
private val coroutineScope: CoroutineScope,
private val logger: AudioLogger,
+ shouldUseVolumeController: Boolean,
) : AudioRepository {
+ private val volumeController = ProducingVolumeController()
private val streamSettingNames: Map<AudioStream, String> =
mapOf(
AudioStream(AudioManager.STREAM_VOICE_CALL) to Settings.System.VOLUME_VOICE,
@@ -116,12 +130,19 @@ class AudioRepositoryImpl(
AudioStream(AudioManager.STREAM_ASSISTANT) to Settings.System.VOLUME_ASSISTANT,
)
+ override val volumeControllerEvents: Flow<VolumeControllerEvent> =
+ if (shouldUseVolumeController) {
+ volumeController.events
+ } else {
+ emptyFlow()
+ }
+
override val mode: StateFlow<Int> =
callbackFlow {
- val listener = AudioManager.OnModeChangedListener { newMode -> trySend(newMode) }
- audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener)
- awaitClose { audioManager.removeOnModeChangedListener(listener) }
- }
+ val listener = AudioManager.OnModeChangedListener { newMode -> trySend(newMode) }
+ audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener)
+ awaitClose { audioManager.removeOnModeChangedListener(listener) }
+ }
.onStart { emit(audioManager.mode) }
.flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
@@ -141,14 +162,14 @@ class AudioRepositoryImpl(
override val communicationDevice: StateFlow<AudioDeviceInfo?>
get() =
callbackFlow {
- val listener = OnCommunicationDeviceChangedListener { trySend(Unit) }
- audioManager.addOnCommunicationDeviceChangedListener(
- ConcurrentUtils.DIRECT_EXECUTOR,
- listener,
- )
+ val listener = OnCommunicationDeviceChangedListener { trySend(Unit) }
+ audioManager.addOnCommunicationDeviceChangedListener(
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ listener,
+ )
- awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) }
- }
+ awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) }
+ }
.filterNotNull()
.map { audioManager.communicationDevice }
.onStart { emit(audioManager.communicationDevice) }
@@ -159,20 +180,30 @@ class AudioRepositoryImpl(
audioManager.communicationDevice,
)
+ override fun init() {
+ try {
+ audioManager.volumeController = volumeController
+ } catch (error: SecurityException) {
+ Log.wtf("AudioManager", "Unable to set the volume controller", error)
+ }
+ }
+
override fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
return merge(
- audioManagerEventsReceiver.events.filter {
- if (it is StreamAudioManagerEvent) {
- it.audioStream == audioStream
- } else {
- true
- }
- },
- volumeSettingChanges(audioStream),
- )
+ audioManagerEventsReceiver.events.filter {
+ if (it is StreamAudioManagerEvent) {
+ it.audioStream == audioStream
+ } else {
+ true
+ }
+ },
+ volumeSettingChanges(audioStream),
+ volumeControllerEvents.filter { it is VolumeControllerEvent.VolumeChanged },
+ )
.conflate()
.map { getCurrentAudioStream(audioStream) }
.onStart { emit(getCurrentAudioStream(audioStream)) }
+ .distinctUntilChanged()
.onEach { logger.onVolumeUpdateReceived(audioStream, it) }
.flowOn(backgroundCoroutineContext)
}
@@ -228,6 +259,12 @@ class AudioRepositoryImpl(
}
}
+ override suspend fun notifyVolumeControllerVisible(isVisible: Boolean) {
+ withContext(backgroundCoroutineContext) {
+ audioManager.notifyVolumeControllerVisible(volumeController, isVisible)
+ }
+ }
+
private fun getMinVolume(stream: AudioStream): Int =
try {
audioManager.getStreamMinVolume(stream.value)
@@ -253,3 +290,45 @@ class AudioRepositoryImpl(
}
}
}
+
+private class ProducingVolumeController : IVolumeController.Stub() {
+
+ private val mutableEvents = MutableSharedFlow<VolumeControllerEvent>(extraBufferCapacity = 32)
+ val events = mutableEvents.asSharedFlow()
+
+ override fun displaySafeVolumeWarning(flags: Int) {
+ mutableEvents.tryEmit(VolumeControllerEvent.DisplaySafeVolumeWarning(flags))
+ }
+
+ override fun volumeChanged(streamType: Int, flags: Int) {
+ mutableEvents.tryEmit(VolumeControllerEvent.VolumeChanged(streamType, flags))
+ }
+
+ override fun masterMuteChanged(flags: Int) {
+ mutableEvents.tryEmit(VolumeControllerEvent.MasterMuteChanged(flags))
+ }
+
+ override fun setLayoutDirection(layoutDirection: Int) {
+ mutableEvents.tryEmit(VolumeControllerEvent.SetLayoutDirection(layoutDirection))
+ }
+
+ override fun dismiss() {
+ mutableEvents.tryEmit(VolumeControllerEvent.Dismiss)
+ }
+
+ override fun setA11yMode(mode: Int) {
+ mutableEvents.tryEmit(VolumeControllerEvent.SetA11yMode(mode))
+ }
+
+ override fun displayCsdWarning(
+ csdWarning: Int,
+ displayDurationMs: Int,
+ ) {
+ mutableEvents.tryEmit(
+ VolumeControllerEvent.DisplayCsdWarning(
+ csdWarning,
+ displayDurationMs,
+ )
+ )
+ }
+}
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
index 0e43acb04c91..52e639172af5 100644
--- 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
@@ -44,6 +44,7 @@ import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -111,6 +112,7 @@ class AudioRepositoryTest {
testScope.testScheduler,
testScope.backgroundScope,
logger,
+ true,
)
}
@@ -261,8 +263,8 @@ class AudioRepositoryTest {
@Test
fun getBluetoothAudioDeviceCategory() {
testScope.runTest {
- `when`(audioManager.getBluetoothAudioDeviceCategory("12:34:56:78")).thenReturn(
- AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES)
+ `when`(audioManager.getBluetoothAudioDeviceCategory("12:34:56:78"))
+ .thenReturn(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES)
val category = underTest.getBluetoothAudioDeviceCategory("12:34:56:78")
runCurrent()
@@ -271,6 +273,27 @@ class AudioRepositoryTest {
}
}
+ @Test
+ fun useVolumeControllerDisabled_setVolumeController_notCalled() {
+ testScope.runTest {
+ underTest =
+ AudioRepositoryImpl(
+ eventsReceiver,
+ audioManager,
+ contentResolver,
+ testScope.testScheduler,
+ testScope.backgroundScope,
+ logger,
+ false,
+ )
+
+ underTest.volumeControllerEvents.launchIn(backgroundScope)
+ runCurrent()
+
+ verify(audioManager, never()).volumeController = any()
+ }
+ }
+
private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) {
verify(audioManager)
.addOnCommunicationDeviceChangedListener(
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/volume/data/repository/AudioRepositoryVolumeControllerEventsTest.kt
index 83b612df8dc9..f5c2f0174456 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/data/repository/AudioManagerVolumeControllerExtTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryVolumeControllerEventsTest.kt
@@ -14,12 +14,15 @@
* limitations under the License.
*/
-package com.android.settingslib.media.data.repository
+package com.android.settingslib.volume.data.repository
+import android.content.ContentResolver
import android.media.AudioManager
import android.media.IVolumeController
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.data.model.VolumeControllerEvent
+import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -39,16 +42,32 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-class AudioManagerVolumeControllerExtTest {
+class AudioRepositoryVolumeControllerEventsTest {
private val testScope = TestScope()
@Captor private lateinit var volumeControllerCaptor: ArgumentCaptor<IVolumeController>
@Mock private lateinit var audioManager: AudioManager
+ @Mock private lateinit var contentResolver: ContentResolver
+
+ private val logger = FakeAudioRepositoryLogger()
+ private val eventsReceiver = FakeAudioManagerEventsReceiver()
+
+ private lateinit var underTest: AudioRepository
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ underTest =
+ AudioRepositoryImpl(
+ eventsReceiver,
+ audioManager,
+ contentResolver,
+ testScope.testScheduler,
+ testScope.backgroundScope,
+ logger,
+ true,
+ )
}
@Test
@@ -83,7 +102,7 @@ class AudioManagerVolumeControllerExtTest {
) =
testScope.runTest {
var event: VolumeControllerEvent? = null
- audioManager.volumeControllerEvents().onEach { event = it }.launchIn(backgroundScope)
+ underTest.volumeControllerEvents.onEach { event = it }.launchIn(backgroundScope)
runCurrent()
verify(audioManager).volumeController = volumeControllerCaptor.capture()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerAdapter.kt
index 68591910031d..e8367315c3c9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerCollector.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeControllerAdapter.kt
@@ -17,7 +17,8 @@
package com.android.systemui.volume
import android.media.IVolumeController
-import com.android.settingslib.media.data.repository.VolumeControllerEvent
+import com.android.settingslib.volume.data.model.VolumeControllerEvent
+import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -29,17 +30,17 @@ import kotlinx.coroutines.launch
* [com.android.settingslib.volume.data.repository.AudioRepository.volumeControllerEvents] and the
* old code that uses [IVolumeController] interface directly.
*/
-class VolumeControllerCollector
+class VolumeControllerAdapter
@Inject
-constructor(@Application private val coroutineScope: CoroutineScope) {
+constructor(
+ @Application private val coroutineScope: CoroutineScope,
+ private val audioRepository: AudioRepository,
+) {
/** Collects [Flow] of [VolumeControllerEvent] into [IVolumeController]. */
- fun collectToController(
- eventsFlow: Flow<VolumeControllerEvent>,
- controller: IVolumeController
- ) =
+ fun collectToController(controller: IVolumeController) {
coroutineScope.launch {
- eventsFlow.collect { event ->
+ audioRepository.volumeControllerEvents.collect { event ->
when (event) {
is VolumeControllerEvent.VolumeChanged ->
controller.volumeChanged(event.streamType, event.flags)
@@ -56,4 +57,9 @@ constructor(@Application private val coroutineScope: CoroutineScope) {
}
}
}
+ }
+
+ fun notifyVolumeControllerVisible(isVisible: Boolean) {
+ coroutineScope.launch { audioRepository.notifyVolumeControllerVisible(isVisible) }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 1522cc490b43..d3e8bd3d8b5b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -68,6 +68,7 @@ import androidx.lifecycle.Observer;
import com.android.internal.annotations.GuardedBy;
import com.android.settingslib.volume.MediaSessions;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
@@ -153,6 +154,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private final KeyguardManager mKeyguardManager;
private final ActivityManager mActivityManager;
private final UserTracker mUserTracker;
+ private final VolumeControllerAdapter mVolumeControllerAdapter;
protected C mCallbacks = new C();
private final State mState = new State();
protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
@@ -197,6 +199,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
NotificationManager notificationManager,
VibratorHelper vibrator,
IAudioService iAudioService,
+ VolumeControllerAdapter volumeControllerAdapter,
AccessibilityManager accessibilityManager,
PackageManager packageManager,
WakefulnessLifecycle wakefulnessLifecycle,
@@ -233,6 +236,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
mVibrator = vibrator;
mHasVibrator = mVibrator.hasVibrator();
mAudioService = iAudioService;
+ mVolumeControllerAdapter = volumeControllerAdapter;
mKeyguardManager = keyguardManager;
mActivityManager = activityManager;
mUserTracker = userTracker;
@@ -259,10 +263,14 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
protected void setVolumeController() {
- try {
- mAudio.setVolumeController(mVolumeController);
- } catch (SecurityException e) {
- Log.w(TAG, "Unable to set the volume controller", e);
+ if (Flags.useVolumeController()) {
+ mVolumeControllerAdapter.collectToController(mVolumeController);
+ } else {
+ try {
+ mAudio.setVolumeController(mVolumeController);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Unable to set the volume controller", e);
+ }
}
}
@@ -384,7 +392,11 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
}
public void notifyVisible(boolean visible) {
- mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
+ if (Flags.useVolumeController()) {
+ mVolumeControllerAdapter.notifyVolumeControllerVisible(visible);
+ } else {
+ mWorker.obtainMessage(W.NOTIFY_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
+ }
}
public void userActivity() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 68d12f69215a..536403ca970e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -20,9 +20,9 @@ import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
import android.content.Context;
import android.content.res.Configuration;
-import android.os.Handler;
import android.util.Log;
+import com.android.settingslib.volume.data.repository.AudioRepository;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.qs.tiles.DndTile;
@@ -39,23 +39,26 @@ public class VolumeUI implements CoreStartable, ConfigurationController.Configur
private static final String TAG = "VolumeUI";
private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
- private final Handler mHandler = new Handler();
-
private boolean mEnabled;
private final Context mContext;
private VolumeDialogComponent mVolumeComponent;
private AudioSharingInteractor mAudioSharingInteractor;
+ private AudioRepository mAudioRepository;
@Inject
- public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent,
+ public VolumeUI(Context context,
+ VolumeDialogComponent volumeDialogComponent,
+ AudioRepository audioRepository,
AudioSharingInteractor audioSharingInteractor) {
mContext = context;
mVolumeComponent = volumeDialogComponent;
+ mAudioRepository = audioRepository;
mAudioSharingInteractor = audioSharingInteractor;
}
@Override
public void start() {
+ mAudioRepository.init();
boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
boolean enableSafetyWarning =
mContext.getResources().getBoolean(R.bool.enable_safety_warning);
@@ -77,7 +80,8 @@ public class VolumeUI implements CoreStartable, ConfigurationController.Configur
@Override
public void dump(PrintWriter pw, String[] args) {
- pw.print("mEnabled="); pw.println(mEnabled);
+ pw.print("mEnabled=");
+ pw.println(mEnabled);
if (!mEnabled) return;
mVolumeComponent.dump(pw, args);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index d39daafd2311..20d598a9334b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -70,6 +70,7 @@ interface AudioModule {
coroutineContext,
coroutineScope,
volumeLogger,
+ com.android.systemui.Flags.useVolumeController(),
)
@Provides
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt
index dd78e4a1fdaa..c1403649efdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerCollectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeControllerAdapterTest.kt
@@ -19,16 +19,17 @@ 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.settingslib.volume.data.model.VolumeControllerEvent
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.audioRepository
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.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.eq
@@ -38,14 +39,20 @@ import org.mockito.kotlin.verify
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
-class VolumeControllerCollectorTest : SysuiTestCase() {
+class VolumeControllerAdapterTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val eventsFlow = MutableStateFlow<VolumeControllerEvent?>(null)
- private val underTest = VolumeControllerCollector(kosmos.applicationCoroutineScope)
+ private val underTest =
+ with(kosmos) { VolumeControllerAdapter(applicationCoroutineScope, audioRepository) }
private val volumeController = mock<IVolumeController> {}
+ @Before
+ fun setUp() {
+ kosmos.audioRepository.init()
+ }
+
@Test
fun volumeControllerEvent_volumeChanged_callsMethod() =
testEvent(VolumeControllerEvent.VolumeChanged(3, 0)) {
@@ -90,7 +97,8 @@ class VolumeControllerCollectorTest : SysuiTestCase() {
private fun testEvent(event: VolumeControllerEvent, verify: () -> Unit) =
kosmos.testScope.runTest {
- underTest.collectToController(eventsFlow.filterNotNull(), volumeController)
+ kosmos.audioRepository.sendVolumeControllerEvent(event)
+ underTest.collectToController(volumeController)
eventsFlow.value = event
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 4ea1a0ca9f2b..f62beeb16ae5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -48,9 +48,11 @@ import androidx.test.filters.SmallTest;
import com.android.settingslib.flags.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestCaseExtKt;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.kosmos.Kosmos;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.VibratorHelper;
@@ -78,6 +80,8 @@ import java.util.concurrent.Executor;
@TestableLooper.RunWithLooper
public class VolumeDialogControllerImplTest extends SysuiTestCase {
+ private final Kosmos mKosmos = SysuiTestCaseExtKt.testKosmos(this);
+
TestableVolumeDialogControllerImpl mVolumeController;
VolumeDialogControllerImpl.C mCallback;
@Mock
@@ -146,6 +150,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
mNotificationManager,
mVibrator,
mIAudioService,
+ VolumeControllerAdapterKosmosKt.getVolumeControllerAdapter(mKosmos),
mAccessibilityManager,
mPackageManager,
mWakefullnessLifcycle,
@@ -323,6 +328,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
NotificationManager notificationManager,
VibratorHelper optionalVibrator,
IAudioService iAudioService,
+ VolumeControllerAdapter volumeControllerAdapter,
AccessibilityManager accessibilityManager,
PackageManager packageManager,
WakefulnessLifecycle wakefulnessLifecycle,
@@ -342,6 +348,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase {
notificationManager,
optionalVibrator,
iAudioService,
+ volumeControllerAdapter,
accessibilityManager,
packageManager,
wakefulnessLifecycle,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt
new file mode 100644
index 000000000000..98cea9d92561
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTestKt.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.app.activityManager
+import android.app.keyguardManager
+import android.content.applicationContext
+import android.content.packageManager
+import android.media.AudioManager
+import android.media.IVolumeController
+import android.os.Handler
+import android.os.looper
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.TestableLooper
+import android.view.accessibility.accessibilityManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.data.model.VolumeControllerEvent
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.wakefulnessLifecycle
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.RingerModeLiveData
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.concurrency.FakeThreadFactory
+import com.android.systemui.util.time.fakeSystemClock
+import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.domain.interactor.audioSharingInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class VolumeDialogControllerImplTestKt : SysuiTestCase() {
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val kosmos: Kosmos = testKosmos()
+ private val audioManager: AudioManager = mock {}
+ private val callbacks: VolumeDialogController.Callbacks = mock {}
+
+ private lateinit var threadFactory: FakeThreadFactory
+ private lateinit var underTest: VolumeDialogControllerImpl
+
+ @Before
+ fun setUp() =
+ with(kosmos) {
+ audioRepository.init()
+ threadFactory =
+ FakeThreadFactory(FakeExecutor(fakeSystemClock)).apply { setLooper(looper) }
+ underTest =
+ VolumeDialogControllerImpl(
+ applicationContext,
+ mock {},
+ mock {
+ on { ringerMode }.thenReturn(mock<RingerModeLiveData> {})
+ on { ringerModeInternal }.thenReturn(mock<RingerModeLiveData> {})
+ },
+ threadFactory,
+ audioManager,
+ mock {},
+ mock {},
+ mock {},
+ volumeControllerAdapter,
+ accessibilityManager,
+ packageManager,
+ wakefulnessLifecycle,
+ keyguardManager,
+ activityManager,
+ mock { on { userContext }.thenReturn(applicationContext) },
+ dumpManager,
+ audioSharingInteractor,
+ mock {},
+ )
+ .apply {
+ setEnableDialogs(true, true)
+ addCallback(callbacks, Handler(looper))
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_USE_VOLUME_CONTROLLER)
+ fun useVolumeControllerEnabled_listensToVolumeController() =
+ testVolumeController { stream: Int, flags: Int ->
+ audioRepository.sendVolumeControllerEvent(
+ VolumeControllerEvent.VolumeChanged(streamType = stream, flags = flags)
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_USE_VOLUME_CONTROLLER)
+ fun useVolumeControllerDisabled_listensToVolumeController() =
+ testVolumeController { stream: Int, flags: Int ->
+ audioManager.emitVolumeChange(stream, flags)
+ }
+
+ private fun testVolumeController(
+ emitVolumeChange: suspend Kosmos.(stream: Int, flags: Int) -> Unit
+ ) =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(wakefulnessLifecycle.wakefulness)
+ .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE)
+ underTest.setVolumeController()
+ runCurrent()
+
+ emitVolumeChange(AudioManager.STREAM_SYSTEM, AudioManager.FLAG_SHOW_UI)
+ runCurrent()
+ TestableLooper.get(this@VolumeDialogControllerImplTestKt).processAllMessages()
+
+ verify(callbacks) { 1 * { onShowRequested(any(), any(), any()) } }
+ }
+ }
+
+ private companion object {
+
+ private fun AudioManager.emitVolumeChange(stream: Int, flags: Int = 0) {
+ val captor = argumentCaptor<IVolumeController>()
+ verify(this) { 1 * { volumeController = captor.capture() } }
+ captor.firstValue.volumeChanged(stream, flags)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerAdapterKosmos.kt
index d60f14cab28f..4045135b928e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerAdapterKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.volume
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.data.repository.audioRepository
-val Kosmos.volumeControllerCollector by
- Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+val Kosmos.volumeControllerAdapter by
+ Kosmos.Fixture { VolumeControllerAdapter(applicationCoroutineScope, audioRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index 135cb14a3497..1fa6c3f2327b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -18,12 +18,16 @@ package com.android.systemui.volume.data.repository
import android.media.AudioDeviceInfo
import android.media.AudioManager
+import com.android.settingslib.volume.data.model.VolumeControllerEvent
import com.android.settingslib.volume.data.repository.AudioRepository
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.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
@@ -39,10 +43,26 @@ class FakeAudioRepository : AudioRepository {
override val communicationDevice: StateFlow<AudioDeviceInfo?> =
mutableCommunicationDevice.asStateFlow()
+ private val mutableVolumeControllerEvents = MutableSharedFlow<VolumeControllerEvent>(replay = 1)
+ override val volumeControllerEvents: Flow<VolumeControllerEvent>
+ get() = mutableVolumeControllerEvents.asSharedFlow()
+
private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf()
private val lastAudibleVolumes: MutableMap<AudioStream, Int> = mutableMapOf()
private val deviceCategories: MutableMap<String, Int> = mutableMapOf()
+ private val mutableIsVolumeControllerVisible = MutableStateFlow(false)
+ val isVolumeControllerVisible: StateFlow<Boolean>
+ get() = mutableIsVolumeControllerVisible.asStateFlow()
+
+ private var mutableIsInitialized: Boolean = false
+ val isInitialized: Boolean
+ get() = mutableIsInitialized
+
+ override fun init() {
+ mutableIsInitialized = true
+ }
+
private fun getAudioStreamModelState(
audioStream: AudioStream
): MutableStateFlow<AudioStreamModel> =
@@ -111,4 +131,16 @@ class FakeAudioRepository : AudioRepository {
override suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int {
return deviceCategories[bluetoothAddress] ?: AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN
}
+
+ suspend fun sendVolumeControllerEvent(event: VolumeControllerEvent) {
+ if (isInitialized) {
+ mutableVolumeControllerEvents.emit(event)
+ }
+ }
+
+ override suspend fun notifyVolumeControllerVisible(isVisible: Boolean) {
+ if (isInitialized) {
+ mutableIsVolumeControllerVisible.value = isVisible
+ }
+ }
}