fix(eq): stop the phone speaker from impersonating a Bluetooth device
Build & Deploy PluriWave / Análisis de código (push) Successful in 27s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m39s

deviceToMap handed the builtin_speaker id to EVERY output type its `when`
did not name. A car stereo on LE Audio (TYPE_BLE_HEADSET) or an automotive
bus (TYPE_BUS) therefore arrived in Dart under the phone speaker's own id,
carrying a type that maps to `desconocido` -- which slipped past the
type-only esBase guard and persisted a device entry keyed builtin_speaker.
From that moment on, every playback through the phone's own speaker matched
that entry, so the green active-output dot stayed pinned to whatever the user
had renamed it to (a car, in the reported case) whether or not anything was
connected. The dot was never wrong; the row was poisoned.

Give unnamed output types their own `other:<type>:<address>` id namespace,
and match esBase by id as well as by type so no future native regression can
re-create the collision. A guarded one-time migration purges what the
collision already persisted from all three device-keyed maps.

Fix the ranking too: builtin_speaker sat inside the priority list as a peer,
so any type absent from that list sorted BELOW the always-present speaker
and could never win. The speaker is now the explicit last resort, externally
connected outputs outrank it, and virtual or call-only sinks (earpiece,
telephony, remote submix, SCO) are ranked below it so they can never be
reported as where music is playing.

Route every AudioDeviceInfo.getAddress read through a version-guarded
helper. It is API 28 with minSdk 24, and two pre-existing unguarded calls in
this same method were latent NoSuchMethodError crashes on Android 7-8.1.
Android lint for :app goes from 8 errors to 6.

Also lets the user manage the list, which is how they recover from a bad
entry without waiting for a release: a remove action clears a device's
preset, name and matrix entries, unnamed rows show their transport and
address tail instead of a raw bt_a2dp:AA:BB:... id, and the green dot
finally carries a tooltip and a semantics label saying what it means.

Device QA pending for wired and USB outputs: no jack or adapter available to
exercise those paths. Their detection is unchanged by this commit.
This commit is contained in:
2026-07-25 16:10:36 +02:00
parent eee2ae98d0
commit 39ead7bea4
36 changed files with 694 additions and 41 deletions
+18
View File
@@ -2420,6 +2420,24 @@ abstract class AppLocalizations {
/// **'Conectado'**
String get eqDeviceConnected;
/// No description provided for @eqDeviceActiveOutput.
///
/// In es, this message translates to:
/// **'El audio está saliendo por este dispositivo'**
String get eqDeviceActiveOutput;
/// No description provided for @eqDeviceRemove.
///
/// In es, this message translates to:
/// **'Quitar dispositivo'**
String get eqDeviceRemove;
/// No description provided for @eqDeviceRemoved.
///
/// In es, this message translates to:
/// **'Se quitó {device}'**
String eqDeviceRemoved(Object device);
/// No description provided for @localMusicSectionTitle.
///
/// In es, this message translates to:
+11
View File
@@ -1313,6 +1313,17 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get eqDeviceConnected => 'متصل';
@override
String get eqDeviceActiveOutput => 'يتم إخراج الصوت من هذا الجهاز';
@override
String get eqDeviceRemove => 'إزالة الجهاز';
@override
String eqDeviceRemoved(Object device) {
return 'تمت إزالة $device';
}
@override
String get localMusicSectionTitle => 'الموسيقى المحلية (Android Auto)';
+11
View File
@@ -1321,6 +1321,17 @@ class AppLocalizationsBn extends AppLocalizations {
@override
String get eqDeviceConnected => 'সংযুক্ত';
@override
String get eqDeviceActiveOutput => 'অডিও এই ডিভাইস দিয়ে বাজছে';
@override
String get eqDeviceRemove => 'ডিভাইস সরান';
@override
String eqDeviceRemoved(Object device) {
return '$device সরানো হয়েছে';
}
@override
String get localMusicSectionTitle => 'স্থানীয় সঙ্গীত (Android Auto)';
+12
View File
@@ -1331,6 +1331,18 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get eqDeviceConnected => 'Verbunden';
@override
String get eqDeviceActiveOutput =>
'Der Ton wird über dieses Gerät ausgegeben';
@override
String get eqDeviceRemove => 'Gerät entfernen';
@override
String eqDeviceRemoved(Object device) {
return '$device entfernt';
}
@override
String get localMusicSectionTitle => 'Lokale Musik (Android Auto)';
+11
View File
@@ -1317,6 +1317,17 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get eqDeviceConnected => 'Connected';
@override
String get eqDeviceActiveOutput => 'Audio is playing through this device';
@override
String get eqDeviceRemove => 'Remove device';
@override
String eqDeviceRemoved(Object device) {
return '$device removed';
}
@override
String get localMusicSectionTitle => 'Local music (Android Auto)';
+12
View File
@@ -1326,6 +1326,18 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get eqDeviceConnected => 'Conectado';
@override
String get eqDeviceActiveOutput =>
'El audio está saliendo por este dispositivo';
@override
String get eqDeviceRemove => 'Quitar dispositivo';
@override
String eqDeviceRemoved(Object device) {
return 'Se quitó $device';
}
@override
String get localMusicSectionTitle => 'Música local (Android Auto)';
+11
View File
@@ -1336,6 +1336,17 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get eqDeviceConnected => 'Connecté';
@override
String get eqDeviceActiveOutput => 'Le son sort par cet appareil';
@override
String get eqDeviceRemove => 'Supprimer lappareil';
@override
String eqDeviceRemoved(Object device) {
return '$device supprimé';
}
@override
String get localMusicSectionTitle => 'Musique locale (Android Auto)';
+11
View File
@@ -1319,6 +1319,17 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get eqDeviceConnected => 'कनेक्टेड';
@override
String get eqDeviceActiveOutput => 'ऑडियो इस डिवाइस से चल रहा है';
@override
String get eqDeviceRemove => 'डिवाइस हटाएं';
@override
String eqDeviceRemoved(Object device) {
return '$device हटा दिया गया';
}
@override
String get localMusicSectionTitle => 'स्थानीय संगीत (Android Auto)';
+11
View File
@@ -1325,6 +1325,17 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get eqDeviceConnected => 'Terhubung';
@override
String get eqDeviceActiveOutput => 'Audio keluar melalui perangkat ini';
@override
String get eqDeviceRemove => 'Hapus perangkat';
@override
String eqDeviceRemoved(Object device) {
return '$device dihapus';
}
@override
String get localMusicSectionTitle => 'Musik lokal (Android Auto)';
+11
View File
@@ -1331,6 +1331,17 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get eqDeviceConnected => 'Connesso';
@override
String get eqDeviceActiveOutput => 'L\'audio esce da questo dispositivo';
@override
String get eqDeviceRemove => 'Rimuovi dispositivo';
@override
String eqDeviceRemoved(Object device) {
return '$device rimosso';
}
@override
String get localMusicSectionTitle => 'Musica locale (Android Auto)';
+11
View File
@@ -1278,6 +1278,17 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get eqDeviceConnected => '接続中';
@override
String get eqDeviceActiveOutput => 'この機器から音声が出力されています';
@override
String get eqDeviceRemove => '機器を削除';
@override
String eqDeviceRemoved(Object device) {
return '$device を削除しました';
}
@override
String get localMusicSectionTitle => 'ローカル音楽(Android Auto';
+11
View File
@@ -1323,6 +1323,17 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get eqDeviceConnected => 'Conectado';
@override
String get eqDeviceActiveOutput => 'O áudio está a sair por este dispositivo';
@override
String get eqDeviceRemove => 'Remover dispositivo';
@override
String eqDeviceRemoved(Object device) {
return '$device removido';
}
@override
String get localMusicSectionTitle => 'Música local (Android Auto)';
+11
View File
@@ -1327,6 +1327,17 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get eqDeviceConnected => 'Подключено';
@override
String get eqDeviceActiveOutput => 'Звук выводится через это устройство';
@override
String get eqDeviceRemove => 'Удалить устройство';
@override
String eqDeviceRemoved(Object device) {
return '$device удалено';
}
@override
String get localMusicSectionTitle => 'Локальная музыка (Android Auto)';
+11
View File
@@ -1271,6 +1271,17 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get eqDeviceConnected => '已连接';
@override
String get eqDeviceActiveOutput => '音频正通过此设备播放';
@override
String get eqDeviceRemove => '移除设备';
@override
String eqDeviceRemoved(Object device) {
return '已移除 $device';
}
@override
String get localMusicSectionTitle => '本地音乐(Android Auto';