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.settingslib.volume.data.repository
18 
19 import android.content.ContentResolver
20 import android.database.ContentObserver
21 import android.media.AudioDeviceInfo
22 import android.media.AudioManager
23 import android.media.AudioManager.AudioDeviceCategory
24 import android.media.AudioManager.OnCommunicationDeviceChangedListener
25 import android.provider.Settings
26 import androidx.concurrent.futures.DirectExecutor
27 import com.android.internal.util.ConcurrentUtils
28 import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
29 import com.android.settingslib.volume.shared.model.AudioManagerEvent
30 import com.android.settingslib.volume.shared.model.AudioStream
31 import com.android.settingslib.volume.shared.model.AudioStreamModel
32 import com.android.settingslib.volume.shared.model.RingerMode
33 import com.android.settingslib.volume.shared.model.StreamAudioManagerEvent
34 import kotlin.coroutines.CoroutineContext
35 import kotlinx.coroutines.CoroutineScope
36 import kotlinx.coroutines.channels.awaitClose
37 import kotlinx.coroutines.flow.Flow
38 import kotlinx.coroutines.flow.SharingStarted
39 import kotlinx.coroutines.flow.StateFlow
40 import kotlinx.coroutines.flow.callbackFlow
41 import kotlinx.coroutines.flow.conflate
42 import kotlinx.coroutines.flow.emptyFlow
43 import kotlinx.coroutines.flow.filter
44 import kotlinx.coroutines.flow.filterIsInstance
45 import kotlinx.coroutines.flow.filterNotNull
46 import kotlinx.coroutines.flow.flowOn
47 import kotlinx.coroutines.flow.map
48 import kotlinx.coroutines.flow.merge
49 import kotlinx.coroutines.flow.onStart
50 import kotlinx.coroutines.flow.stateIn
51 import kotlinx.coroutines.launch
52 import kotlinx.coroutines.withContext
53 
54 /** Provides audio streams state and managing functionality. */
55 interface AudioRepository {
56 
57     /** Current [AudioManager.getMode]. */
58     val mode: StateFlow<Int>
59 
60     /**
61      * Ringtone mode.
62      *
63      * @see AudioManager.getRingerModeInternal
64      */
65     val ringerMode: StateFlow<RingerMode>
66 
67     /**
68      * Communication device. Emits null when there is no communication device available.
69      *
70      * @see AudioDeviceInfo.getType
71      */
72     val communicationDevice: StateFlow<AudioDeviceInfo?>
73 
74     /** State of the [AudioStream]. */
75     fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel>
76 
77     /** Returns the last audible volume before stream was muted. */
78     suspend fun getLastAudibleVolume(audioStream: AudioStream): Int
79 
80     suspend fun setVolume(audioStream: AudioStream, volume: Int)
81 
82     /**
83      * Mutes and un-mutes [audioStream]. Returns true when the state changes and false the
84      * otherwise.
85      */
86     suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean
87 
88     suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
89 
90     /** Gets audio device category. */
91     @AudioDeviceCategory
92     suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int
93 }
94 
95 class AudioRepositoryImpl(
96     private val audioManagerEventsReceiver: AudioManagerEventsReceiver,
97     private val audioManager: AudioManager,
98     private val contentResolver: ContentResolver,
99     private val backgroundCoroutineContext: CoroutineContext,
100     private val coroutineScope: CoroutineScope,
101 ) : AudioRepository {
102 
103     private val streamSettingNames: Map<AudioStream, String> =
104         mapOf(
105             AudioStream(AudioManager.STREAM_VOICE_CALL) to Settings.System.VOLUME_VOICE,
106             AudioStream(AudioManager.STREAM_SYSTEM) to Settings.System.VOLUME_SYSTEM,
107             AudioStream(AudioManager.STREAM_RING) to Settings.System.VOLUME_RING,
108             AudioStream(AudioManager.STREAM_MUSIC) to Settings.System.VOLUME_MUSIC,
109             AudioStream(AudioManager.STREAM_ALARM) to Settings.System.VOLUME_ALARM,
110             AudioStream(AudioManager.STREAM_NOTIFICATION) to Settings.System.VOLUME_NOTIFICATION,
111             AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to Settings.System.VOLUME_BLUETOOTH_SCO,
112             AudioStream(AudioManager.STREAM_ACCESSIBILITY) to Settings.System.VOLUME_ACCESSIBILITY,
113             AudioStream(AudioManager.STREAM_ASSISTANT) to Settings.System.VOLUME_ASSISTANT,
114         )
115 
116     override val mode: StateFlow<Int> =
<lambda>null117         callbackFlow {
118                 val listener = AudioManager.OnModeChangedListener { newMode -> trySend(newMode) }
119                 audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener)
120                 awaitClose { audioManager.removeOnModeChangedListener(listener) }
121             }
<lambda>null122             .onStart { emit(audioManager.mode) }
123             .flowOn(backgroundCoroutineContext)
124             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
125 
126     override val ringerMode: StateFlow<RingerMode> =
127         audioManagerEventsReceiver.events
128             .filterIsInstance(AudioManagerEvent.InternalRingerModeChanged::class)
<lambda>null129             .map { RingerMode(audioManager.ringerModeInternal) }
<lambda>null130             .onStart { emit(RingerMode(audioManager.ringerModeInternal)) }
131             .flowOn(backgroundCoroutineContext)
132             .stateIn(
133                 coroutineScope,
134                 SharingStarted.WhileSubscribed(),
135                 RingerMode(audioManager.ringerModeInternal),
136             )
137 
138     override val communicationDevice: StateFlow<AudioDeviceInfo?>
139         get() =
<lambda>null140             callbackFlow {
141                     val listener = OnCommunicationDeviceChangedListener { trySend(Unit) }
142                     audioManager.addOnCommunicationDeviceChangedListener(
143                         ConcurrentUtils.DIRECT_EXECUTOR,
144                         listener,
145                     )
146 
147                     awaitClose { audioManager.removeOnCommunicationDeviceChangedListener(listener) }
148                 }
149                 .filterNotNull()
<lambda>null150                 .map { audioManager.communicationDevice }
<lambda>null151                 .onStart { emit(audioManager.communicationDevice) }
152                 .flowOn(backgroundCoroutineContext)
153                 .stateIn(
154                     coroutineScope,
155                     SharingStarted.WhileSubscribed(),
156                     audioManager.communicationDevice,
157                 )
158 
getAudioStreamnull159     override fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
160         return merge(
161                 audioManagerEventsReceiver.events.filter {
162                     if (it is StreamAudioManagerEvent) {
163                         it.audioStream == audioStream
164                     } else {
165                         true
166                     }
167                 },
168                 volumeSettingChanges(audioStream),
169             )
170             .conflate()
171             .map { getCurrentAudioStream(audioStream) }
172             .onStart { emit(getCurrentAudioStream(audioStream)) }
173             .flowOn(backgroundCoroutineContext)
174     }
175 
getCurrentAudioStreamnull176     private fun getCurrentAudioStream(audioStream: AudioStream): AudioStreamModel {
177         return AudioStreamModel(
178             audioStream = audioStream,
179             minVolume = getMinVolume(audioStream),
180             maxVolume = audioManager.getStreamMaxVolume(audioStream.value),
181             volume = audioManager.getStreamVolume(audioStream.value),
182             isAffectedByMute = audioManager.isStreamMutableByUi(audioStream.value),
183             isAffectedByRingerMode = audioManager.isStreamAffectedByRingerMode(audioStream.value),
184             isMuted = audioManager.isStreamMute(audioStream.value),
185         )
186     }
187 
getLastAudibleVolumenull188     override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int {
189         return withContext(backgroundCoroutineContext) {
190             audioManager.getLastAudibleStreamVolume(audioStream.value)
191         }
192     }
193 
setVolumenull194     override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
195         withContext(backgroundCoroutineContext) {
196             audioManager.setStreamVolume(audioStream.value, volume, 0)
197         }
198     }
199 
setMutednull200     override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean {
201         return withContext(backgroundCoroutineContext) {
202             if (isMuted == audioManager.isStreamMute(audioStream.value)) {
203                 false
204             } else {
205                 audioManager.adjustStreamVolume(
206                     audioStream.value,
207                     if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
208                     0,
209                 )
210                 true
211             }
212         }
213     }
214 
setRingerModenull215     override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
216         withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value }
217     }
218 
219     @AudioDeviceCategory
getBluetoothAudioDeviceCategorynull220     override suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int {
221         return withContext(backgroundCoroutineContext) {
222             audioManager.getBluetoothAudioDeviceCategory(bluetoothAddress)
223         }
224     }
225 
getMinVolumenull226     private fun getMinVolume(stream: AudioStream): Int =
227         try {
228             audioManager.getStreamMinVolume(stream.value)
229         } catch (e: IllegalArgumentException) {
230             // Fallback to STREAM_VOICE_CALL because
231             // CallVolumePreferenceController.java default
232             // return STREAM_VOICE_CALL in getAudioStream
233             audioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL)
234         }
235 
volumeSettingChangesnull236     private fun volumeSettingChanges(audioStream: AudioStream): Flow<Unit> {
237         val uri = streamSettingNames[audioStream]?.let(Settings.System::getUriFor)
238         uri ?: return emptyFlow()
239         return callbackFlow {
240             val observer =
241                 object : ContentObserver(DirectExecutor.INSTANCE, 0) {
242                     override fun onChange(selfChange: Boolean) {
243                         launch { send(Unit) }
244                     }
245                 }
246             contentResolver.registerContentObserver(uri, false, observer)
247             awaitClose { contentResolver.unregisterContentObserver(observer) }
248         }
249     }
250 }
251