Skip to content

Commit be1c9b7

Browse files
bmartyElementBot
andauthored
Fix audio output selection for Element Call (#4602)
* Fix audio output selection. * Ensure that Element Call audio output uses a new connected device, even during a call. Also add a few logs. * Extract functions. * Add more log and protect from crash. * Revert formatting change * Update screenshots --------- Co-authored-by: ElementBot <[email protected]>
1 parent 7ed362b commit be1c9b7

File tree

4 files changed

+62
-17
lines changed

4 files changed

+62
-17
lines changed

features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
package io.element.android.features.call.impl.ui
99

1010
import android.annotation.SuppressLint
11+
import android.content.Context
12+
import android.media.AudioDeviceCallback
13+
import android.media.AudioDeviceInfo
1114
import android.media.AudioManager
1215
import android.util.Log
1316
import android.view.ViewGroup
@@ -22,6 +25,10 @@ import androidx.compose.foundation.layout.fillMaxSize
2225
import androidx.compose.foundation.layout.padding
2326
import androidx.compose.material3.ExperimentalMaterial3Api
2427
import androidx.compose.runtime.Composable
28+
import androidx.compose.runtime.getValue
29+
import androidx.compose.runtime.mutableStateOf
30+
import androidx.compose.runtime.remember
31+
import androidx.compose.runtime.setValue
2532
import androidx.compose.ui.Alignment
2633
import androidx.compose.ui.Modifier
2734
import androidx.compose.ui.platform.LocalInspectionMode
@@ -151,15 +158,11 @@ private fun CallWebView(
151158
Text("WebView - can't be previewed")
152159
}
153160
} else {
161+
var audioDeviceCallback: AudioDeviceCallback? by remember { mutableStateOf(null) }
154162
AndroidView(
155163
modifier = modifier,
156164
factory = { context ->
157-
// Set 'voice call' mode so volume keys actually control the call volume
158-
val audioManager = context.getSystemService<AudioManager>()
159-
audioManager?.mode = AudioManager.MODE_IN_COMMUNICATION
160-
161-
audioManager?.enableExternalAudioDevice()
162-
165+
audioDeviceCallback = context.setupAudioConfiguration()
163166
WebView(context).apply {
164167
onWebViewCreate(this)
165168
setup(userAgent, onPermissionsRequest)
@@ -172,16 +175,40 @@ private fun CallWebView(
172175
},
173176
onRelease = { webView ->
174177
// Reset audio mode
175-
val audioManager = webView.context.getSystemService<AudioManager>()
176-
audioManager?.disableExternalAudioDevice()
177-
audioManager?.mode = AudioManager.MODE_NORMAL
178-
178+
webView.context.releaseAudioConfiguration(audioDeviceCallback)
179179
webView.destroy()
180180
}
181181
)
182182
}
183183
}
184184

185+
private fun Context.setupAudioConfiguration(): AudioDeviceCallback? {
186+
val audioManager = getSystemService<AudioManager>() ?: return null
187+
// Set 'voice call' mode so volume keys actually control the call volume
188+
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
189+
audioManager.enableExternalAudioDevice()
190+
return object : AudioDeviceCallback() {
191+
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
192+
Timber.d("Audio devices added")
193+
audioManager.enableExternalAudioDevice()
194+
}
195+
196+
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
197+
Timber.d("Audio devices removed")
198+
audioManager.enableExternalAudioDevice()
199+
}
200+
}.also {
201+
audioManager.registerAudioDeviceCallback(it, null)
202+
}
203+
}
204+
205+
private fun Context.releaseAudioConfiguration(audioDeviceCallback: AudioDeviceCallback?) {
206+
val audioManager = getSystemService<AudioManager>() ?: return
207+
audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
208+
audioManager.disableExternalAudioDevice()
209+
audioManager.mode = AudioManager.MODE_NORMAL
210+
}
211+
185212
@SuppressLint("SetJavaScriptEnabled")
186213
private fun WebView.setup(
187214
userAgent: String,

libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/AudioManager.kt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ package io.element.android.libraries.androidutils.compat
1010
import android.media.AudioDeviceInfo
1111
import android.media.AudioManager
1212
import android.os.Build
13+
import io.element.android.libraries.core.data.tryOrNull
14+
import timber.log.Timber
1315

1416
fun AudioManager.enableExternalAudioDevice() {
1517
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -30,10 +32,26 @@ fun AudioManager.enableExternalAudioDevice() {
3032
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
3133
)
3234
val devices = availableCommunicationDevices
33-
val selectedDevice = devices.find {
34-
wantedDeviceTypes.contains(it.type)
35+
val selectedDevice = devices.minByOrNull {
36+
wantedDeviceTypes.indexOf(it.type).let { index ->
37+
// If the device type is not in the wantedDeviceTypes list, we give it a low priority
38+
if (index == -1) Int.MAX_VALUE else index
39+
}
40+
}
41+
selectedDevice?.let { device ->
42+
Timber.d("Audio device selected, type: ${device.type}")
43+
tryOrNull(
44+
onError = { failure ->
45+
Timber.e(failure, "Audio: exception when setting communication device")
46+
}
47+
) {
48+
setCommunicationDevice(device).also {
49+
if (!it) {
50+
Timber.w("Audio: unable to set the communication device")
51+
}
52+
}
53+
}
3554
}
36-
selectedDevice?.let { setCommunicationDevice(it) }
3755
} else {
3856
// If we don't have access to the new APIs, use the deprecated ones
3957
@Suppress("DEPRECATION")
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading

0 commit comments

Comments
 (0)