Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5743ddc5 authored by Yiyi Shen's avatar Yiyi Shen
Browse files

[Audiosharing] Add dynamic stream for audio sharing

Part 2: convert audio sharing callbacks to secondary headset volume changes

When sharing audio with two headsets, add a dynamic stream to control
the volume of secondary headset while keep using STREAM_MUSIC to control
primary headset.

Test: atest
Bug: 336716411
Flag: com.android.settingslib.flags.volume_dialog_audio_sharing_fix
Change-Id: Ibdf66368c32b3fc3b84a333e4446f02ef75fe684
parent 1e49841f
Loading
Loading
Loading
Loading
+100 −0
Original line number Diff line number Diff line
/*
 * 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.domain.interactor

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.volume.data.repository.audioSharingRepository
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
class AudioSharingInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    lateinit var underTest: AudioSharingInteractor

    @Before
    fun setUp() {
        with(kosmos) { underTest = audioSharingInteractor }
    }

    @Test
    fun volumeChanges_returnVolume() {
        with(kosmos) {
            testScope.runTest {
                with(audioSharingRepository) {
                    setSecondaryGroupId(TEST_GROUP_ID)
                    setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
                }
                val volume by collectLastValue(underTest.volume)
                runCurrent()

                Truth.assertThat(volume).isEqualTo(TEST_VOLUME)
            }
        }
    }

    @Test
    fun volumeChanges_returnNull() {
        with(kosmos) {
            testScope.runTest {
                with(audioSharingRepository) {
                    setSecondaryGroupId(TEST_GROUP_ID_INVALID)
                    setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
                }
                val volume by collectLastValue(underTest.volume)
                runCurrent()

                Truth.assertThat(volume).isNull()
            }
        }
    }

    @Test
    fun volumeChanges_returnDefaultVolume() {
        with(kosmos) {
            testScope.runTest {
                with(audioSharingRepository) {
                    setSecondaryGroupId(TEST_GROUP_ID)
                    setVolumeMap(emptyMap())
                }
                val volume by collectLastValue(underTest.volume)
                runCurrent()

                Truth.assertThat(volume).isEqualTo(TEST_VOLUME_DEFAULT)
            }
        }
    }

    private companion object {
        const val TEST_GROUP_ID = 1
        const val TEST_GROUP_ID_INVALID = -1
        const val TEST_VOLUME = 10
        const val TEST_VOLUME_DEFAULT = 20
    }
}
+35 −0
Original line number Diff line number Diff line
/*
 * 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.dagger

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
import com.android.systemui.volume.domain.interactor.AudioSharingInteractorEmptyImpl
import dagger.Module
import dagger.Provides

/** Dagger module for empty audio sharing impl for unnecessary volume overlay */
@Module
interface AudioSharingEmptyImplModule {

    companion object {
        @Provides
        @SysUISingleton
        fun provideAudioSharingInteractor(): AudioSharingInteractor =
            AudioSharingInteractorEmptyImpl()
    }
}
+40 −0
Original line number Diff line number Diff line
/*
 * 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.dagger

import com.android.settingslib.volume.data.repository.AudioSharingRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
import com.android.systemui.volume.domain.interactor.AudioSharingInteractorImpl
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineScope

/** Dagger module for audio sharing code in the volume package */
@Module
interface AudioSharingModule {

    companion object {
        @Provides
        @SysUISingleton
        fun provideAudioSharingInteractor(
            @Application coroutineScope: CoroutineScope,
            repository: AudioSharingRepository
        ): AudioSharingInteractor = AudioSharingInteractorImpl(coroutineScope, repository)
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import dagger.multibindings.IntoSet;
@Module(
        includes = {
                AudioModule.class,
                AudioSharingModule.class,
                AncModule.class,
                CaptioningModule.class,
                MediaDevicesModule.class,
+90 −0
Original line number Diff line number Diff line
/*
 * 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.domain.interactor

import android.bluetooth.BluetoothCsipSetCoordinator
import androidx.annotation.IntRange
import com.android.settingslib.volume.data.repository.AudioSharingRepository
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch

interface AudioSharingInteractor {
    /** Audio sharing secondary headset volume changes. */
    val volume: Flow<Int?>

    /** Audio sharing secondary headset min volume. */
    val volumeMin: Int

    /** Audio sharing secondary headset max volume. */
    val volumeMax: Int

    /** Set the volume of the secondary headset in audio sharing. */
    fun setStreamVolume(
        @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
        level: Int
    )
}

@SysUISingleton
class AudioSharingInteractorImpl
@Inject
constructor(
    @Application private val coroutineScope: CoroutineScope,
    private val audioSharingRepository: AudioSharingRepository
) : AudioSharingInteractor {

    override val volume: Flow<Int?> =
        combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) {
            secondaryGroupId,
            volumeMap ->
            if (secondaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) null
            else volumeMap.getOrDefault(secondaryGroupId, DEFAULT_VOLUME)
        }

    override val volumeMin: Int = AUDIO_SHARING_VOLUME_MIN

    override val volumeMax: Int = AUDIO_SHARING_VOLUME_MAX

    override fun setStreamVolume(level: Int) {
        coroutineScope.launch { audioSharingRepository.setSecondaryVolume(level) }
    }

    private companion object {
        const val DEFAULT_VOLUME = 20
    }
}

@SysUISingleton
class AudioSharingInteractorEmptyImpl : AudioSharingInteractor {
    override val volume: Flow<Int?> = emptyFlow()
    override val volumeMin: Int = EMPTY_VOLUME
    override val volumeMax: Int = EMPTY_VOLUME

    override fun setStreamVolume(level: Int) {}

    private companion object {
        const val EMPTY_VOLUME = 0
    }
}
Loading