1 /*
<lambda>null2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.systemui.volume.dialog.sliders.domain.interactor
18
19 import android.content.pm.PackageManager
20 import android.media.AudioManager
21 import android.media.AudioSystem
22 import com.android.settingslib.flags.Flags
23 import com.android.systemui.volume.VolumeDialogControllerImpl
24 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
25 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
26 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
27 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
28 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
29 import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
30 import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSlidersModel
31 import javax.inject.Inject
32 import kotlinx.coroutines.CoroutineScope
33 import kotlinx.coroutines.flow.Flow
34 import kotlinx.coroutines.flow.SharingStarted
35 import kotlinx.coroutines.flow.filter
36 import kotlinx.coroutines.flow.filterNotNull
37 import kotlinx.coroutines.flow.map
38 import kotlinx.coroutines.flow.runningReduce
39 import kotlinx.coroutines.flow.stateIn
40
41 private const val DEFAULT_STREAM = AudioManager.STREAM_MUSIC
42
43 /** Provides a state for the Sliders section of the Volume Dialog. */
44 @VolumeDialogScope
45 class VolumeDialogSlidersInteractor
46 @Inject
47 constructor(
48 volumeDialogStateInteractor: VolumeDialogStateInteractor,
49 private val packageManager: PackageManager,
50 @VolumeDialog private val coroutineScope: CoroutineScope,
51 ) {
52
53 private val streamsSorter = StreamsSorter()
54 val sliders: Flow<VolumeDialogSlidersModel> =
55 volumeDialogStateInteractor.volumeDialogState
56 .filter { it.streamModels.isNotEmpty() }
57 .map { stateModel ->
58 val sliderTypes =
59 stateModel.streamModels.values
60 .filter { streamModel -> shouldShowSliders(stateModel, streamModel) }
61 .sortedWith(streamsSorter)
62 .map { model -> model.toType() }
63 LinkedHashSet(sliderTypes)
64 }
65 .runningReduce { sliderTypes, newSliderTypes ->
66 sliderTypes.apply { addAll(newSliderTypes) }
67 }
68 .map { sliderTypes ->
69 VolumeDialogSlidersModel(
70 slider = sliderTypes.first(),
71 floatingSliders = sliderTypes.drop(1),
72 )
73 }
74 .stateIn(coroutineScope, SharingStarted.Eagerly, null)
75 .filterNotNull()
76
77 private fun shouldShowSliders(
78 stateModel: VolumeDialogStateModel,
79 streamModel: VolumeDialogStreamModel,
80 ): Boolean {
81 if (streamModel.isActive) {
82 return true
83 }
84
85 if (!packageManager.isTv()) {
86 if (streamModel.stream == AudioSystem.STREAM_ACCESSIBILITY) {
87 return stateModel.shouldShowA11ySlider
88 }
89
90 // Always show the stream for audio sharing if it exists.
91 if (
92 (Flags.volumeDialogAudioSharingFix() || Flags.audioSharingDeveloperOption()) &&
93 streamModel.stream == VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST
94 ) {
95 return true
96 }
97
98 return streamModel.stream == DEFAULT_STREAM || streamModel.isDynamic
99 }
100
101 return false
102 }
103
104 private fun VolumeDialogStreamModel.toType(): VolumeDialogSliderType {
105 return when {
106 stream == VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST ->
107 VolumeDialogSliderType.AudioSharingStream(stream)
108 stream >= VolumeDialogControllerImpl.DYNAMIC_STREAM_REMOTE_START_INDEX ->
109 VolumeDialogSliderType.RemoteMediaStream(stream)
110 else -> VolumeDialogSliderType.Stream(stream)
111 }
112 }
113
114 private class StreamsSorter : Comparator<VolumeDialogStreamModel> {
115
116 /**
117 * This list reflects the order of the sorted collection. Elements that satisfy predicates
118 * at the beginning of this list will be earlier in the sorted collection.
119 */
120 private val priorityPredicates: List<(VolumeDialogStreamModel) -> Boolean> =
121 listOf(
122 { it.isActive },
123 { it.stream == AudioManager.STREAM_MUSIC },
124 { it.stream == AudioManager.STREAM_ACCESSIBILITY },
125 { it.stream == AudioManager.STREAM_RING },
126 { it.stream == AudioManager.STREAM_NOTIFICATION },
127 { it.stream == AudioManager.STREAM_VOICE_CALL },
128 { it.stream == AudioManager.STREAM_SYSTEM },
129 { it.isDynamic },
130 )
131
132 override fun compare(lhs: VolumeDialogStreamModel, rhs: VolumeDialogStreamModel): Int {
133 return lhs.getPriority() - rhs.getPriority()
134 }
135
136 private fun VolumeDialogStreamModel.getPriority(): Int {
137 val index = priorityPredicates.indexOfFirst { it(this) }
138 return if (index >= 0) {
139 index
140 } else {
141 stream
142 }
143 }
144 }
145 }
146
PackageManagernull147 private fun PackageManager.isTv(): Boolean = hasSystemFeature(PackageManager.FEATURE_LEANBACK)
148