Skip to content

Commit 22a36e9

Browse files
committed
Tweak the UX a bit, only display the audio device selector in Android 12+
1 parent 72311a5 commit 22a36e9

File tree

1 file changed

+72
-36
lines changed
  • features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui

1 file changed

+72
-36
lines changed

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

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import android.webkit.PermissionRequest
2121
import android.webkit.WebChromeClient
2222
import android.webkit.WebView
2323
import androidx.activity.compose.BackHandler
24+
import androidx.compose.foundation.layout.Arrangement
2425
import androidx.compose.foundation.layout.Box
2526
import androidx.compose.foundation.layout.Column
27+
import androidx.compose.foundation.layout.Row
2628
import androidx.compose.foundation.layout.consumeWindowInsets
2729
import androidx.compose.foundation.layout.fillMaxSize
2830
import androidx.compose.foundation.layout.fillMaxWidth
@@ -63,8 +65,11 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
6365
import io.element.android.libraries.designsystem.theme.components.Button
6466
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
6567
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
68+
import io.element.android.libraries.designsystem.theme.components.Icon
69+
import io.element.android.libraries.designsystem.theme.components.IconSource
6670
import io.element.android.libraries.designsystem.theme.components.Scaffold
6771
import io.element.android.libraries.designsystem.theme.components.Text
72+
import io.element.android.libraries.designsystem.theme.components.TextButton
6873
import io.element.android.libraries.designsystem.theme.components.TopAppBar
6974
import io.element.android.libraries.ui.strings.CommonStrings
7075
import timber.log.Timber
@@ -97,12 +102,15 @@ internal fun CallScreenView(
97102
topBar = {
98103
if (!pipState.isInPictureInPicture) {
99104
TopAppBar(
100-
title = { Text(stringResource(R.string.element_call)) },
105+
title = {},
101106
navigationIcon = {
102107
BackButton(
103108
imageVector = if (pipState.supportPip) CompoundIcons.ArrowLeft() else CompoundIcons.Close(),
104109
onClick = ::handleBack,
105110
)
111+
},
112+
actions = {
113+
AudioDeviceSelector()
106114
}
107115
)
108116
}
@@ -173,8 +181,6 @@ private fun CallWebView(
173181
Column(modifier = modifier) {
174182
var audioDeviceCallback: AudioDeviceCallback? by remember { mutableStateOf(null) }
175183

176-
OutputAudioDeviceSelector()
177-
178184
AndroidView(
179185
modifier = Modifier.fillMaxWidth(),
180186
factory = { context ->
@@ -200,66 +206,80 @@ private fun CallWebView(
200206
}
201207

202208
@Composable
203-
private fun OutputAudioDeviceSelector() {
209+
private fun AudioDeviceSelector(
210+
modifier: Modifier = Modifier,
211+
) {
212+
// For now don't display the audio device selector in unsupported Android versions
213+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
214+
return
215+
}
204216
val context = LocalContext.current
205217
val audioManager = remember { context.getSystemService<AudioManager>() }
206218

207219
val audioDevices = remember { mutableStateListOf<AudioDeviceInfo>() }
208220
var expanded by remember { mutableStateOf(false) }
221+
val isInEditMode = LocalInspectionMode.current
209222
var selected by remember(audioDevices) {
210-
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
223+
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !isInEditMode) {
211224
audioManager?.communicationDevice
212225
} else {
213226
null
214227
}
215228
mutableStateOf(device)
216229
}
217230

218-
DisposableEffect(Unit) {
219-
audioDevices.addAll(audioManager?.loadOutputAudioDevices().orEmpty())
231+
if (!LocalInspectionMode.current) {
232+
DisposableEffect(Unit) {
233+
audioDevices.addAll(audioManager?.loadCommunicationAudioDevices().orEmpty())
220234

221-
val onCommunicationDeviceChangedListener = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
222-
OnCommunicationDeviceChangedListener { selected = audioManager?.communicationDevice }
223-
.also { audioManager?.addOnCommunicationDeviceChangedListener(Executors.newSingleThreadExecutor(), it) }
224-
} else {
225-
null
226-
}
227-
228-
val audioDeviceCallback = object : AudioDeviceCallback() {
229-
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
230-
audioDevices.clear()
231-
audioDevices.addAll(audioManager?.loadOutputAudioDevices().orEmpty())
235+
val onCommunicationDeviceChangedListener = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
236+
OnCommunicationDeviceChangedListener { selected = audioManager?.communicationDevice }
237+
.also { audioManager?.addOnCommunicationDeviceChangedListener(Executors.newSingleThreadExecutor(), it) }
238+
} else {
239+
null
232240
}
233241

234-
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
235-
audioDevices.clear()
236-
audioDevices.addAll(audioManager?.loadOutputAudioDevices().orEmpty())
242+
val audioDeviceCallback = object : AudioDeviceCallback() {
243+
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
244+
audioDevices.clear()
245+
audioDevices.addAll(audioManager?.loadCommunicationAudioDevices().orEmpty())
246+
}
247+
248+
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
249+
audioDevices.clear()
250+
audioDevices.addAll(audioManager?.loadCommunicationAudioDevices().orEmpty())
251+
}
237252
}
238-
}
239-
audioManager?.registerAudioDeviceCallback(audioDeviceCallback, null)
253+
audioManager?.registerAudioDeviceCallback(audioDeviceCallback, null)
240254

241-
onDispose {
242-
audioManager?.unregisterAudioDeviceCallback(audioDeviceCallback)
255+
onDispose {
256+
audioManager?.unregisterAudioDeviceCallback(audioDeviceCallback)
243257

244-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
245-
onCommunicationDeviceChangedListener?.let { audioManager?.removeOnCommunicationDeviceChangedListener(it) }
258+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
259+
onCommunicationDeviceChangedListener?.let { audioManager?.removeOnCommunicationDeviceChangedListener(it) }
260+
}
246261
}
247262
}
248263
}
249264

250-
Box(Modifier.padding(horizontal = 16.dp)) {
251-
Button(
252-
modifier = Modifier.fillMaxWidth(),
253-
text = "Audio output: ${selected?.description() ?: "-"}",
265+
Box(modifier.padding(horizontal = 16.dp)) {
266+
TextButton(
267+
text = "Audio device",
254268
onClick = {
255269
expanded = !expanded
256-
}
270+
},
271+
leadingIcon = IconSource.Vector(CompoundIcons.ChevronDown()),
257272
)
258273

259274
DropdownMenu(expanded, onDismissRequest = { expanded = false }) {
260275
for (device in audioDevices) {
261276
DropdownMenuItem(text = {
262-
Text(device.description())
277+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
278+
Text(device.description())
279+
if (selected == device) {
280+
Icon(imageVector = CompoundIcons.Check(), contentDescription = null)
281+
}
282+
}
263283
}, onClick = {
264284
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
265285
// Workaround for Android 12, otherwise changing the audio device doesn't work
@@ -300,7 +320,7 @@ private fun OutputAudioDeviceSelector() {
300320
}
301321
}
302322

303-
private fun AudioManager.loadOutputAudioDevices(): List<AudioDeviceInfo> {
323+
private fun AudioManager.loadCommunicationAudioDevices(): List<AudioDeviceInfo> {
304324
val wantedDeviceTypes = listOf(
305325
// Paired bluetooth device with microphone
306326
AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
@@ -328,7 +348,7 @@ private fun AudioManager.loadOutputAudioDevices(): List<AudioDeviceInfo> {
328348

329349
fun AudioDeviceInfo.description(): String {
330350
val type = when (type) {
331-
AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "Bluetooth SCO"
351+
AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "Bluetooth"
332352
AudioDeviceInfo.TYPE_USB_ACCESSORY -> "USB accessory"
333353
AudioDeviceInfo.TYPE_USB_DEVICE -> "USB device"
334354
AudioDeviceInfo.TYPE_USB_HEADSET -> "USB headset"
@@ -338,9 +358,25 @@ fun AudioDeviceInfo.description(): String {
338358
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "Built-in earpiece"
339359
else -> "Unknown device type: $type"
340360
}
341-
return "$productName - $type"
361+
return if (isBuiltIn()) {
362+
type
363+
} else {
364+
val name = if (productName.length > 10) {
365+
productName.substring(0, 10) + ""
366+
} else {
367+
productName
368+
}
369+
"$name - $type"
370+
}
342371
}
343372

373+
private fun AudioDeviceInfo.isBuiltIn(): Boolean = when (type) {
374+
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
375+
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
376+
AudioDeviceInfo.TYPE_BUILTIN_MIC,
377+
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE -> true
378+
else -> false}
379+
344380
private fun Context.setupAudioConfiguration(): AudioDeviceCallback? {
345381
val audioManager = getSystemService<AudioManager>() ?: return null
346382
// Set 'voice call' mode so volume keys actually control the call volume

0 commit comments

Comments
 (0)