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

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

Merge "Filter and sort sliders for BC25 Volume Dialog" into main

parents 93b2b3ee f5d495d1
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ private val dialogTimeoutDuration = 3.seconds
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper()
@TestableLooper.RunWithLooper
class VolumeDialogVisibilityInteractorTest : SysuiTestCase() {

    private val kosmos: Kosmos = testKosmos()
+163 −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.dialog.sliders.domain.interactor

import android.content.packageManager
import android.content.pm.PackageManager
import android.media.AudioManager
import android.testing.TestableLooper
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.plugins.VolumeDialogController
import com.android.systemui.plugins.fakeVolumeDialogController
import com.android.systemui.testKosmos
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
import com.google.common.truth.Truth.assertThat
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
import org.mockito.kotlin.whenever

private const val AUDIO_SHARING_STREAM = 99

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class VolumeDialogSlidersInteractorTest : SysuiTestCase() {

    private val kosmos = testKosmos()

    private lateinit var underTest: VolumeDialogSlidersInteractor

    private var isTv: Boolean = false

    @Before
    fun setUp() {
        with(kosmos) {
            whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)).thenAnswer {
                isTv
            }

            underTest = kosmos.volumeDialogSlidersInteractor
        }
    }

    @Test
    fun activeStreamIsSlider() =
        with(kosmos) {
            testScope.runTest {
                runCurrent()
                fakeVolumeDialogController.updateState {
                    activeStream = AudioManager.STREAM_SYSTEM
                    states.put(AudioManager.STREAM_MUSIC, buildStreamState())
                    states.put(AudioManager.STREAM_SYSTEM, buildStreamState())
                }

                val slidersModel by collectLastValue(underTest.sliders)
                runCurrent()

                assertThat(slidersModel!!.slider)
                    .isEqualTo(VolumeDialogSliderType.Stream(AudioManager.STREAM_SYSTEM))
                assertThat(slidersModel!!.floatingSliders)
                    .isEqualTo(listOf(VolumeDialogSliderType.Stream(AudioManager.STREAM_MUSIC)))
            }
        }

    @Test
    fun streamsOrder() =
        with(kosmos) {
            testScope.runTest {
                runCurrent()
                fakeVolumeDialogController.onAccessibilityModeChanged(true)
                fakeVolumeDialogController.updateState {
                    activeStream = AudioManager.STREAM_MUSIC
                    states.put(AUDIO_SHARING_STREAM, buildStreamState { dynamic = true })
                    states.put(AUDIO_SHARING_STREAM + 1, buildStreamState { dynamic = true })
                    states.put(AudioManager.STREAM_MUSIC, buildStreamState())
                    states.put(AudioManager.STREAM_ACCESSIBILITY, buildStreamState())
                }

                val slidersModel by collectLastValue(underTest.sliders)
                runCurrent()

                assertThat(slidersModel!!.slider)
                    .isEqualTo(VolumeDialogSliderType.Stream(AudioManager.STREAM_MUSIC))
                assertThat(slidersModel!!.floatingSliders)
                    .isEqualTo(
                        listOf(
                            VolumeDialogSliderType.Stream(AudioManager.STREAM_ACCESSIBILITY),
                            VolumeDialogSliderType.AudioSharingStream(AUDIO_SHARING_STREAM),
                            VolumeDialogSliderType.RemoteMediaStream(AUDIO_SHARING_STREAM + 1),
                        )
                    )
            }
        }

    @Test
    fun accessibilityStreamDisabled_filteredOut() =
        with(kosmos) {
            testScope.runTest {
                runCurrent()
                fakeVolumeDialogController.onAccessibilityModeChanged(false)
                fakeVolumeDialogController.updateState {
                    states.put(AudioManager.STREAM_ACCESSIBILITY, buildStreamState())
                    states.put(AudioManager.STREAM_MUSIC, buildStreamState())
                }

                val slidersModel by collectLastValue(underTest.sliders)
                runCurrent()

                assertThat(slidersModel!!.slider)
                    .isEqualTo(VolumeDialogSliderType.Stream(AudioManager.STREAM_MUSIC))
                assertThat(slidersModel!!.floatingSliders).isEmpty()
            }
        }

    @Test
    fun isTv_onlyActiveStream() =
        with(kosmos) {
            testScope.runTest {
                runCurrent()
                isTv = true
                fakeVolumeDialogController.updateState {
                    activeStream = AudioManager.STREAM_SYSTEM
                    states.put(AudioManager.STREAM_MUSIC, buildStreamState())
                    states.put(AudioManager.STREAM_SYSTEM, buildStreamState())
                }

                val slidersModel by collectLastValue(underTest.sliders)
                runCurrent()

                assertThat(slidersModel!!.slider)
                    .isEqualTo(VolumeDialogSliderType.Stream(AudioManager.STREAM_SYSTEM))
                assertThat(slidersModel!!.floatingSliders).isEmpty()
            }
        }

    private fun buildStreamState(
        build: VolumeDialogController.StreamState.() -> Unit = {}
    ): VolumeDialogController.StreamState {
        return VolumeDialogController.StreamState().apply(build)
    }
}
+37 −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.dialog.data.repository

import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update

/** Holds current [VolumeDialogStateModel]. */
@VolumeDialogPlugin
class VolumeDialogStateRepository @Inject constructor() {

    private val mutableState = MutableStateFlow(VolumeDialogStateModel())
    val state: Flow<VolumeDialogStateModel> = mutableState.asStateFlow()

    fun updateState(update: (VolumeDialogStateModel) -> VolumeDialogStateModel) {
        mutableState.update(update)
    }
}
+2 −3
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import com.android.systemui.plugins.VolumeDialogController
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.ProducerScope
@@ -50,7 +49,7 @@ constructor(
    @Background private val bgHandler: Handler,
) {

    @SuppressLint("SharedFlowCreation") // event-but needed
    @SuppressLint("SharedFlowCreation") // event-bus needed
    val event: Flow<VolumeDialogEventModel> =
        callbackFlow {
                val producer = VolumeDialogEventModelProducer(this)
@@ -79,7 +78,7 @@ constructor(

        override fun onStateChanged(state: VolumeDialogController.State?) {
            if (state != null) {
                scope.trySend(VolumeDialogEventModel.StateChanged(VolumeDialogStateModel(state)))
                scope.trySend(VolumeDialogEventModel.StateChanged(state))
            }
        }

+67 −11
Original line number Diff line number Diff line
@@ -16,20 +16,21 @@

package com.android.systemui.volume.dialog.domain.interactor

import android.util.SparseArray
import androidx.core.util.keyIterator
import com.android.systemui.plugins.VolumeDialogController
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPlugin
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogPluginScope
import com.android.systemui.volume.dialog.data.repository.VolumeDialogStateRepository
import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel
import com.android.systemui.volume.dialog.domain.model.VolumeDialogStateModel
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn

/**
 * Exposes [VolumeDialogController.getState] in the [volumeDialogState].
@@ -42,14 +43,69 @@ class VolumeDialogStateInteractor
constructor(
    volumeDialogCallbacksInteractor: VolumeDialogCallbacksInteractor,
    private val volumeDialogController: VolumeDialogController,
    private val volumeDialogStateRepository: VolumeDialogStateRepository,
    @VolumeDialogPlugin private val coroutineScope: CoroutineScope,
) {

    val volumeDialogState: Flow<VolumeDialogStateModel> =
    init {
        volumeDialogCallbacksInteractor.event
            .onEach { event ->
                when (event) {
                    is VolumeDialogEventModel.StateChanged -> {
                        volumeDialogStateRepository.updateState { oldState ->
                            event.state.copyIntoModel(oldState)
                        }
                    }
                    is VolumeDialogEventModel.AccessibilityModeChanged -> {
                        volumeDialogStateRepository.updateState { oldState ->
                            oldState.copy(shouldShowA11ySlider = event.showA11yStream)
                        }
                    }
                    else -> {
                        // do nothing
                    }
                }
            }
            .onStart { volumeDialogController.getState() }
            .filterIsInstance(VolumeDialogEventModel.StateChanged::class)
            .map { it.state }
            .stateIn(scope = coroutineScope, started = SharingStarted.Eagerly, initialValue = null)
            .filterNotNull()
            .launchIn(coroutineScope)
    }

    val volumeDialogState: Flow<VolumeDialogStateModel> = volumeDialogStateRepository.state

    /** Returns a copy of [model] filled with the values from [VolumeDialogController.State]. */
    private fun VolumeDialogController.State.copyIntoModel(
        model: VolumeDialogStateModel
    ): VolumeDialogStateModel {
        return model.copy(
            streamModels =
                states.mapToMap { stream, streamState ->
                    VolumeDialogStreamModel(
                        stream = stream,
                        isActive = stream == activeStream,
                        legacyState = streamState,
                    )
                },
            ringerModeInternal = ringerModeInternal,
            ringerModeExternal = ringerModeExternal,
            zenMode = zenMode,
            effectsSuppressor = effectsSuppressor,
            effectsSuppressorName = effectsSuppressorName,
            activeStream = activeStream,
            disallowAlarms = disallowAlarms,
            disallowMedia = disallowMedia,
            disallowSystem = disallowSystem,
            disallowRinger = disallowRinger,
        )
    }
}

private fun <INPUT, OUTPUT> SparseArray<INPUT>.mapToMap(
    map: (Int, INPUT) -> OUTPUT
): Map<Int, OUTPUT> {
    val resultMap = mutableMapOf<Int, OUTPUT>()
    for (key in keyIterator()) {
        val mappedValue: OUTPUT = map(key, get(key)!!)
        resultMap[key] = mappedValue
    }
    return resultMap
}
Loading