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

Commit b8481144 authored by Yiyi Shen's avatar Yiyi Shen Committed by Android (Google) Code Review
Browse files

Merge "[Audiosharing] Add log for AudioSharingRepository" into main

parents a7312406 6de87545
Loading
Loading
Loading
Loading
+22 −28
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.media.AudioManager.OnCommunicationDeviceChangedListener
import android.provider.Settings
import androidx.concurrent.futures.DirectExecutor
import com.android.internal.util.ConcurrentUtils
import com.android.settingslib.volume.shared.AudioLogger
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.settingslib.volume.shared.model.AudioManagerEvent
import com.android.settingslib.volume.shared.model.AudioStream
@@ -99,7 +100,7 @@ class AudioRepositoryImpl(
    private val contentResolver: ContentResolver,
    private val backgroundCoroutineContext: CoroutineContext,
    private val coroutineScope: CoroutineScope,
    private val logger: Logger,
    private val logger: AudioLogger,
) : AudioRepository {

    private val streamSettingNames: Map<AudioStream, String> =
@@ -251,11 +252,4 @@ class AudioRepositoryImpl(
            awaitClose { contentResolver.unregisterContentObserver(observer) }
        }
    }

    interface Logger {

        fun onSetVolumeRequested(audioStream: AudioStream, volume: Int)

        fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel)
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.settingslib.bluetooth.onServiceStateChanged
import com.android.settingslib.bluetooth.onSourceConnectedOrRemoved
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.settingslib.volume.shared.AudioSharingLogger
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,6 +51,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.flow.stateIn
@@ -90,6 +92,7 @@ class AudioSharingRepositoryImpl(
    private val btManager: LocalBluetoothManager,
    private val coroutineScope: CoroutineScope,
    private val backgroundCoroutineContext: CoroutineContext,
    private val logger: AudioSharingLogger
) : AudioSharingRepository {
    private val isAudioSharingProfilesReady: StateFlow<Boolean> =
        btManager.profileManager.onServiceStateChanged
@@ -104,6 +107,7 @@ class AudioSharingRepositoryImpl(
                btManager.profileManager.leAudioBroadcastProfile.onBroadcastStartedOrStopped
                    .map { isBroadcasting() }
                    .onStart { emit(isBroadcasting()) }
                    .onEach { logger.onAudioSharingStateChanged(it) }
                    .flowOn(backgroundCoroutineContext)
            } else {
                flowOf(false)
@@ -156,6 +160,7 @@ class AudioSharingRepositoryImpl(
                .map { getSecondaryGroupId() },
            primaryGroupId.map { getSecondaryGroupId() })
            .onStart { emit(getSecondaryGroupId()) }
            .onEach { logger.onSecondaryGroupIdChanged(it) }
            .flowOn(backgroundCoroutineContext)
            .stateIn(
                coroutineScope,
@@ -202,6 +207,7 @@ class AudioSharingRepositoryImpl(
                            acc
                        }
                    }
                    .onEach { logger.onVolumeMapChanged(it) }
                    .flowOn(backgroundCoroutineContext)
            } else {
                emptyFlow()
@@ -220,6 +226,7 @@ class AudioSharingRepositoryImpl(
                    BluetoothUtils.getSecondaryDeviceForBroadcast(contentResolver, btManager)
                if (cachedDevice != null) {
                    it.setDeviceVolume(cachedDevice.device, volume, /* isGroupOp= */ true)
                    logger.onSetDeviceVolumeRequested(volume)
                }
            }
        }
+27 −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.settingslib.volume.shared

import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel

/** A log interface for audio streams volume events. */
interface AudioLogger {
    fun onSetVolumeRequested(audioStream: AudioStream, volume: Int)

    fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel)
}
 No newline at end of file
+29 −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.settingslib.volume.shared

/** A log interface for audio sharing volume events. */
interface AudioSharingLogger {

    fun onAudioSharingStateChanged(state: Boolean)

    fun onSecondaryGroupIdChanged(groupId: Int)

    fun onVolumeMapChanged(map: Map<Int, Int>)

    fun onSetDeviceVolumeRequested(volume: Int)
}
 No newline at end of file
+70 −17
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -110,6 +111,7 @@ class AudioSharingRepositoryTest {
    @Captor
    private lateinit var volumeCallbackCaptor: ArgumentCaptor<BluetoothVolumeControl.Callback>

    private val logger = FakeAudioSharingRepositoryLogger()
    private val testScope = TestScope()
    private val context: Context = ApplicationProvider.getApplicationContext()
    @Spy private val contentResolver: ContentResolver = context.contentResolver
@@ -135,16 +137,23 @@ class AudioSharingRepositoryTest {
        Settings.Secure.putInt(
            contentResolver,
            BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
            TEST_GROUP_ID_INVALID)
            TEST_GROUP_ID_INVALID
        )
        underTest =
            AudioSharingRepositoryImpl(
                contentResolver,
                btManager,
                testScope.backgroundScope,
                testScope.testScheduler,
                logger
            )
    }

    @After
    fun tearDown() {
        logger.reset()
    }

    @Test
    fun audioSharingStateChange_profileReady_emitValues() {
        testScope.runTest {
@@ -160,6 +169,13 @@ class AudioSharingRepositoryTest {
            runCurrent()

            Truth.assertThat(states).containsExactly(false, true, false, true)
            Truth.assertThat(logger.logs)
                .containsAtLeastElementsIn(
                    listOf(
                        "onAudioSharingStateChanged state=true",
                        "onAudioSharingStateChanged state=false",
                    )
                ).inOrder()
        }
    }

@@ -187,7 +203,8 @@ class AudioSharingRepositoryTest {
            Truth.assertThat(groupIds)
                .containsExactly(
                    TEST_GROUP_ID_INVALID,
                    TEST_GROUP_ID2)
                    TEST_GROUP_ID2
                )
        }
    }

@@ -219,13 +236,16 @@ class AudioSharingRepositoryTest {
            triggerSourceAdded()
            runCurrent()
            triggerProfileConnectionChange(
                BluetoothAdapter.STATE_CONNECTING, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
                BluetoothAdapter.STATE_CONNECTING, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
            )
            runCurrent()
            triggerProfileConnectionChange(
                BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO)
                BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO
            )
            runCurrent()
            triggerProfileConnectionChange(
                BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
                BluetoothAdapter.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
            )
            runCurrent()

            Truth.assertThat(groupIds)
@@ -235,7 +255,16 @@ class AudioSharingRepositoryTest {
                    TEST_GROUP_ID1,
                    TEST_GROUP_ID_INVALID,
                    TEST_GROUP_ID2,
                    TEST_GROUP_ID_INVALID)
                    TEST_GROUP_ID_INVALID
                )
            Truth.assertThat(logger.logs)
                .containsAtLeastElementsIn(
                    listOf(
                        "onSecondaryGroupIdChanged groupId=$TEST_GROUP_ID_INVALID",
                        "onSecondaryGroupIdChanged groupId=$TEST_GROUP_ID2",
                        "onSecondaryGroupIdChanged groupId=$TEST_GROUP_ID1",
                    )
                ).inOrder()
        }
    }

@@ -257,11 +286,22 @@ class AudioSharingRepositoryTest {
            verify(volumeControl).unregisterCallback(any())
            runCurrent()

            val expectedMap1 = mapOf(TEST_GROUP_ID1 to TEST_VOLUME1)
            val expectedMap2 = mapOf(TEST_GROUP_ID1 to TEST_VOLUME2)
            Truth.assertThat(volumeMaps)
                .containsExactly(
                    emptyMap<Int, Int>(),
                    mapOf(TEST_GROUP_ID1 to TEST_VOLUME1),
                    mapOf(TEST_GROUP_ID1 to TEST_VOLUME2))
                    expectedMap1,
                    expectedMap2
                )
            Truth.assertThat(logger.logs)
                .containsAtLeastElementsIn(
                    listOf(
                        "onVolumeMapChanged map={}",
                        "onVolumeMapChanged map=$expectedMap1",
                        "onVolumeMapChanged map=$expectedMap2",
                    )
                ).inOrder()
        }
    }

@@ -281,12 +321,19 @@ class AudioSharingRepositoryTest {
            Settings.Secure.putInt(
                contentResolver,
                BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
                TEST_GROUP_ID2)
                TEST_GROUP_ID2
            )
            `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
            underTest.setSecondaryVolume(TEST_VOLUME1)

            runCurrent()
            verify(volumeControl).setDeviceVolume(device1, TEST_VOLUME1, true)
            Truth.assertThat(logger.logs)
                .isEqualTo(
                    listOf(
                        "onSetVolumeRequested volume=$TEST_VOLUME1",
                    )
                )
        }
    }

@@ -313,7 +360,8 @@ class AudioSharingRepositoryTest {
        Settings.Secure.putInt(
            contentResolver,
            BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
            TEST_GROUP_ID1)
            TEST_GROUP_ID1
        )
        `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
        assistantCallbackCaptor.value.sourceAdded(device1, receiveState)
    }
@@ -324,7 +372,8 @@ class AudioSharingRepositoryTest {
        Settings.Secure.putInt(
            contentResolver,
            BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
            TEST_GROUP_ID1)
            TEST_GROUP_ID1
        )
        assistantCallbackCaptor.value.sourceRemoved(device2)
    }

@@ -334,7 +383,8 @@ class AudioSharingRepositoryTest {
        Settings.Secure.putInt(
            contentResolver,
            BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
            TEST_GROUP_ID1)
            TEST_GROUP_ID1
        )
        btCallbackCaptor.value.onProfileConnectionStateChanged(cachedDevice2, state, profile)
    }

@@ -343,12 +393,14 @@ class AudioSharingRepositoryTest {
            .registerContentObserver(
                eq(Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast())),
                eq(false),
                contentObserverCaptor.capture())
                contentObserverCaptor.capture()
            )
        `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
        Settings.Secure.putInt(
            contentResolver,
            BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
            TEST_GROUP_ID2)
            TEST_GROUP_ID2
        )
        contentObserverCaptor.value.primaryChanged()
    }

@@ -381,7 +433,8 @@ class AudioSharingRepositoryTest {
        }
        val sourceAdded:
                BluetoothLeBroadcastAssistant.Callback.(
                sink: BluetoothDevice, state: BluetoothLeBroadcastReceiveState) -> Unit =
                    sink: BluetoothDevice, state: BluetoothLeBroadcastReceiveState
                ) -> Unit =
            { sink, state ->
                onReceiveStateChanged(sink, TEST_SOURCE_ID, state)
            }
Loading