fix(eq): stop the phone's FM sink from posing as the active output
Regression from the previous commit. Ranking every AudioDeviceInfo type this
build does not name individually ABOVE the built-in speaker was meant to let
a car stereo on LE Audio or an automotive bus win. It also promoted the
internal sinks a phone exposes permanently: on the Xiaomi test device
AudioManager reports TYPE_FM (14) as an output, so getActiveAudioDevice
picked it over the real speaker with nothing connected at all. Confirmed on
device:
audio_devices.onListen -> {id=other:14:4, type=14, name=2412DPC0AG}
It then reached Dart under an `other:14:4` id whose type is neither the base
speaker nor a known one, slipped past the collision guard and had a preset
row persisted for it -- reinstating the exact symptom this series set out to
kill: a permanent green active-output dot on a device that was not connected.
Replace the deny-by-omission ranking with an explicit allow list of outputs a
user actually connects. The built-in speaker sits below all of them and above
everything else, so any sink that physically exists but is never where media
plays (TYPE_FM, TYPE_BUILTIN_SPEAKER_SAFE, telephony, remote submix) can no
longer be selected. A one-time purge clears the `other:` rows the bad build
persisted; genuine ones re-register on their next connection.
Fix the USB type constant while here: TYPE_USB_HEADSET is 22, not 14, and 14
is TYPE_FM. The Kotlin USB branch hardcoded 14 and the Dart type table
mirrored the same mistake, so the two cancelled out for real USB headsets
while making a phone's own FM sink decode as USB audio. Both now use 22.
Verified with javap against android.jar (android-36) rather than trusting the
comment that introduced the error.
This commit is contained in:
@@ -1216,7 +1216,9 @@ class MainActivity : AudioServiceActivity() {
|
||||
* or still the OS placeholder ("02:00:00:00:00:00", seen
|
||||
* without BLUETOOTH_CONNECT); productName colons are
|
||||
* sanitized to '-' to preserve the matrix-key delimiter
|
||||
* - "usb_headset:<address>" — TYPE_USB_HEADSET (14)
|
||||
* - "usb_headset:<address>" — TYPE_USB_HEADSET (22)
|
||||
* - "other:<type>:<address>" — any other external output this build
|
||||
* does not name individually
|
||||
* - "builtin_speaker" — fallback when API < 23
|
||||
*
|
||||
* Type int values sent to Dart match the AudioDeviceInfo.TYPE_* constants.
|
||||
@@ -1240,29 +1242,49 @@ class MainActivity : AudioServiceActivity() {
|
||||
return deviceToMap(best)
|
||||
}
|
||||
|
||||
/**
|
||||
* Media outputs a user actively connects, in the order they should win when
|
||||
* several are present. Index IS the priority.
|
||||
*
|
||||
* This is an ALLOW list on purpose. Ranking "everything not named here"
|
||||
* above the built-in speaker looks equivalent and is not: a phone
|
||||
* permanently exposes internal sinks that are legitimate outputs but never
|
||||
* where music is playing -- TYPE_FM (14) on this project's Xiaomi test
|
||||
* device, TYPE_BUILTIN_SPEAKER_SAFE (24) on many others. Those outranked
|
||||
* the real speaker, got reported as the active device and had a preset row
|
||||
* persisted for them.
|
||||
*/
|
||||
private val externalOutputPriority = listOf(
|
||||
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
|
||||
AudioDeviceInfo.TYPE_BLE_HEADSET,
|
||||
AudioDeviceInfo.TYPE_BLE_SPEAKER,
|
||||
AudioDeviceInfo.TYPE_BLE_BROADCAST,
|
||||
AudioDeviceInfo.TYPE_HEARING_AID,
|
||||
AudioDeviceInfo.TYPE_BUS,
|
||||
AudioDeviceInfo.TYPE_USB_HEADSET,
|
||||
AudioDeviceInfo.TYPE_USB_DEVICE,
|
||||
AudioDeviceInfo.TYPE_USB_ACCESSORY,
|
||||
AudioDeviceInfo.TYPE_WIRED_HEADSET,
|
||||
AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
|
||||
AudioDeviceInfo.TYPE_LINE_ANALOG,
|
||||
AudioDeviceInfo.TYPE_LINE_DIGITAL,
|
||||
AudioDeviceInfo.TYPE_AUX_LINE,
|
||||
AudioDeviceInfo.TYPE_DOCK,
|
||||
AudioDeviceInfo.TYPE_HDMI,
|
||||
AudioDeviceInfo.TYPE_HDMI_ARC,
|
||||
)
|
||||
|
||||
/**
|
||||
* Ranks an [AudioDeviceInfo] type as a media output candidate; lower wins.
|
||||
*
|
||||
* Externally connected outputs outrank the built-in speaker, including types
|
||||
* this build does not name individually. Virtual and call-only sinks
|
||||
* (earpiece, telephony, remote submix) are pushed below the speaker so they
|
||||
* can never be reported as where music is playing.
|
||||
* The built-in speaker is the fallback, so it sits below every external
|
||||
* output and above everything else — including outputs that physically
|
||||
* exist but are never where media plays.
|
||||
*/
|
||||
private fun outputPriority(type: Int): Int = when (type) {
|
||||
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP -> 0
|
||||
AudioDeviceInfo.TYPE_USB_HEADSET -> 1
|
||||
AudioDeviceInfo.TYPE_USB_DEVICE -> 2
|
||||
AudioDeviceInfo.TYPE_WIRED_HEADSET -> 3
|
||||
AudioDeviceInfo.TYPE_WIRED_HEADPHONES -> 4
|
||||
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> 90
|
||||
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
|
||||
AudioDeviceInfo.TYPE_TELEPHONY,
|
||||
AudioDeviceInfo.TYPE_REMOTE_SUBMIX,
|
||||
AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> 99
|
||||
// Everything else is an output the user physically connected (LE Audio,
|
||||
// hearing aids, car bus, dock, HDMI): above the speaker, below the
|
||||
// types named above so a known match always wins a tie.
|
||||
else -> 50
|
||||
private fun outputPriority(type: Int): Int {
|
||||
val index = externalOutputPriority.indexOf(type)
|
||||
if (index >= 0) return index
|
||||
return if (type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) 90 else 99
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1307,7 +1329,10 @@ class MainActivity : AudioServiceActivity() {
|
||||
val addr = deviceAddress(device)?.takeIf { it.isNotBlank() } ?: device.id.toString()
|
||||
mapOf(
|
||||
"id" to "usb_headset:$addr",
|
||||
"type" to 14,
|
||||
// Send the real constant (22). The hardcoded 14 that used to
|
||||
// sit here is TYPE_FM, and Dart mirrored the mistake, so a
|
||||
// phone's own FM sink decoded as a USB headset.
|
||||
"type" to AudioDeviceInfo.TYPE_USB_HEADSET,
|
||||
"name" to (device.productName?.toString() ?: "USB Headset"),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user