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