Loading packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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() Loading packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt 0 → 100644 +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) } } packages/SystemUI/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogStateRepository.kt 0 → 100644 +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) } } packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt +2 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading Loading @@ -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)) } } Loading packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt +67 −11 Original line number Diff line number Diff line Loading @@ -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]. Loading @@ -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
packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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() Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt 0 → 100644 +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) } }
packages/SystemUI/src/com/android/systemui/volume/dialog/data/repository/VolumeDialogStateRepository.kt 0 → 100644 +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) } }
packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt +2 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading Loading @@ -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)) } } Loading
packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogStateInteractor.kt +67 −11 Original line number Diff line number Diff line Loading @@ -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]. Loading @@ -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 }