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

Commit eb649022 authored by Anton Potapov's avatar Anton Potapov Committed by Android (Google) Code Review
Browse files

Merge "Add AudioRepository to provide audio control functionality for the new...

Merge "Add AudioRepository to provide audio control functionality for the new VolumePanel." into main
parents 4575eae8 e86cef6e
Loading
Loading
Loading
Loading
+53 −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.data.repository

import android.media.AudioManager
import com.android.internal.util.ConcurrentUtils
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/** Provides audio managing functionality and data. */
interface AudioRepository {

    /** Current [AudioManager.getMode]. */
    val mode: StateFlow<Int>
}

class AudioRepositoryImpl(
    private val audioManager: AudioManager,
    backgroundCoroutineContext: CoroutineContext,
    coroutineScope: CoroutineScope,
) : AudioRepository {

    override val mode: StateFlow<Int> =
        callbackFlow {
                val listener =
                    AudioManager.OnModeChangedListener { newMode -> launch { send(newMode) } }
                audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener)
                awaitClose { audioManager.removeOnModeChangedListener(listener) }
            }
            .flowOn(backgroundCoroutineContext)
            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
}
+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.settingslib.volume.domain.interactor

import android.media.AudioManager
import com.android.settingslib.volume.data.repository.AudioRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class AudioModeInteractor(repository: AudioRepository) {

    private val ongoingCallModes =
        setOf(
            AudioManager.MODE_RINGTONE,
            AudioManager.MODE_IN_CALL,
            AudioManager.MODE_IN_COMMUNICATION,
        )

    /** Returns if current [AudioManager.getMode] call is an ongoing call */
    val isOngoingCall: Flow<Boolean> = repository.mode.map { it in ongoingCallModes }
}
+1 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ android_test {
        "SettingsLibSettingsSpinner",
        "SettingsLibUsageProgressBarPreference",
        "settingslib_media_flags_lib",
        "kotlinx_coroutines_test",
    ],

    dxflags: ["--multi-dex"],
+32 −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.data.repository

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

class FakeAudioRepository : AudioRepository {

    private val mutableMode = MutableStateFlow(0)
    override val mode: StateFlow<Int>
        get() = mutableMode.asStateFlow()

    fun setMode(newMode: Int) {
        mutableMode.value = newMode
    }
}
+79 −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.domain.interactor

import android.media.AudioManager
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.settingslib.volume.data.repository.FakeAudioRepository
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.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
class AudioModeInteractorTest {

    private val testScope = TestScope()
    private val fakeAudioRepository = FakeAudioRepository()

    private val underTest = AudioModeInteractor(fakeAudioRepository)

    @Test
    fun ongoingCallModes_isOnGoingCall() {
        testScope.runTest {
            for (mode in ongoingCallModes) {
                var isOngoingCall = false
                underTest.isOngoingCall.onEach { isOngoingCall = it }.launchIn(backgroundScope)

                fakeAudioRepository.setMode(mode)
                runCurrent()

                assertThat(isOngoingCall).isTrue()
            }
        }
    }

    @Test
    fun notOngoingCallModes_isNotOnGoingCall() {
        testScope.runTest {
            var isOngoingCall = true
            underTest.isOngoingCall.onEach { isOngoingCall = it }.launchIn(backgroundScope)

            fakeAudioRepository.setMode(AudioManager.MODE_CURRENT)
            runCurrent()

            assertThat(isOngoingCall).isFalse()
        }
    }

    private companion object {
        private val ongoingCallModes =
            setOf(
                AudioManager.MODE_RINGTONE,
                AudioManager.MODE_IN_CALL,
                AudioManager.MODE_IN_COMMUNICATION,
            )
    }
}
Loading