fix android auto quirks

This commit is contained in:
Boof2015
2026-07-13 23:04:28 -04:00
parent 3ac75432a4
commit a8c1f4fd22
22 changed files with 897 additions and 27 deletions
@@ -16,3 +16,7 @@ android {
abortOnError false
}
}
dependencies {
testImplementation 'junit:junit:4.13.2'
}
@@ -1,6 +1,7 @@
package expo.modules.astraaudioroute
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioManager
@@ -162,20 +163,30 @@ class AstraAudioRouteModule : Module() {
} catch (_: Throwable) {
emptyList()
}
if (outputs.isEmpty()) return null
return outputs.firstOrNull { kindForType(it.type) == "bluetooth" }
?: outputs.firstOrNull { kindForType(it.type) == "wired" }
?: outputs.firstOrNull { kindForType(it.type) == "usb" }
?: outputs.firstOrNull { kindForType(it.type) == "hdmi" }
?: outputs.firstOrNull { kindForType(it.type) == "speaker" }
?: outputs.first()
val predicted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
try {
val mediaAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
audioManager().getAudioDevicesForAttributes(mediaAttributes).filter { it.isSink }
} catch (_: Throwable) {
emptyList()
}
} else {
emptyList()
}
return selectPredictedOutputDevice(predicted, outputs) { kindForType(it.type) }
}
private fun kindForType(type: Int): String =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
when (type) {
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "bluetooth"
AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
AudioDeviceInfo.TYPE_BLE_HEADSET,
AudioDeviceInfo.TYPE_BLE_SPEAKER,
AudioDeviceInfo.TYPE_BLE_BROADCAST -> "bluetooth"
AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
AudioDeviceInfo.TYPE_WIRED_HEADSET -> "wired"
AudioDeviceInfo.TYPE_USB_ACCESSORY,
@@ -0,0 +1,17 @@
package expo.modules.astraaudioroute
/** Prefer Android's media-attribute prediction, then retain the legacy fallback. */
internal fun <T> selectPredictedOutputDevice(
predicted: List<T>,
connected: List<T>,
kindFor: (T) -> String,
): T? {
predicted.firstOrNull()?.let { return it }
if (connected.isEmpty()) return null
return connected.firstOrNull { kindFor(it) == "bluetooth" }
?: connected.firstOrNull { kindFor(it) == "wired" }
?: connected.firstOrNull { kindFor(it) == "usb" }
?: connected.firstOrNull { kindFor(it) == "hdmi" }
?: connected.firstOrNull { kindFor(it) == "speaker" }
?: connected.first()
}
@@ -0,0 +1,37 @@
package expo.modules.astraaudioroute
import org.junit.Assert.assertEquals
import org.junit.Test
class OutputDeviceSelectorTest {
private data class Device(val name: String, val kind: String)
@Test
fun predictedMediaDeviceWinsOverConnectedBluetoothPriority() {
val bluetooth = Device("car bluetooth", "bluetooth")
val usb = Device("android auto usb", "usb")
val selected = selectPredictedOutputDevice(
predicted = listOf(usb),
connected = listOf(bluetooth, usb),
kindFor = Device::kind,
)
assertEquals(usb, selected)
}
@Test
fun legacyFallbackKeepsExistingPriorityWithoutPrediction() {
val speaker = Device("phone", "speaker")
val usb = Device("dac", "usb")
val bluetooth = Device("headphones", "bluetooth")
val selected = selectPredictedOutputDevice(
predicted = emptyList(),
connected = listOf(speaker, usb, bluetooth),
kindFor = Device::kind,
)
assertEquals(bluetooth, selected)
}
}