fix(eq): stop the phone speaker from impersonating a Bluetooth device
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:
@@ -264,7 +264,14 @@ class EstadoEcualizador extends ChangeNotifier {
|
||||
// fallback ids for a device whose real MAC is not yet known, so
|
||||
// persisting a preset entry for them would create dead noise that never
|
||||
// resolves to the eventual real-MAC id.
|
||||
final esBase = dispositivo.tipo == TipoDispositivo.altavozInterno;
|
||||
// Matched by id AND by type: the native layer historically reported the
|
||||
// phone-speaker id for output types it could not name (LE Audio, car bus,
|
||||
// dock), which arrive here as `desconocido` and would otherwise slip past a
|
||||
// type-only check and persist an entry that hijacks the active-device
|
||||
// indicator forever.
|
||||
final esBase =
|
||||
dispositivo.tipo == TipoDispositivo.altavozInterno ||
|
||||
dispositivo.id == idAltavozInterno;
|
||||
final esPlaceholderCompuesto = dispositivo.id.startsWith(
|
||||
prefijoPlaceholderBtName,
|
||||
);
|
||||
@@ -424,6 +431,35 @@ class EstadoEcualizador extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Forgets [deviceId] completely: its device preset, its custom name and
|
||||
/// every matrix entry that targets it.
|
||||
///
|
||||
/// Lets the user clear stale or duplicate rows from the known-devices list.
|
||||
/// The device is NOT prevented from coming back: if it connects again it is
|
||||
/// re-registered from scratch, which is exactly how a user recovers from a
|
||||
/// bad entry. When the removed device is the active one, the effective preset
|
||||
/// is re-resolved so playback immediately follows the remaining hierarchy
|
||||
/// instead of keeping the deleted preset applied.
|
||||
Future<void> eliminarDispositivo(String deviceId) async {
|
||||
_presetsDispositivo.remove(deviceId);
|
||||
_nombresDispositivos.remove(deviceId);
|
||||
_nombresPlataforma.remove(deviceId);
|
||||
_presetsMatriz.removeWhere((clave, _) {
|
||||
final separador = clave.indexOf(':');
|
||||
if (separador == -1) return false;
|
||||
return clave.substring(separador + 1) == deviceId;
|
||||
});
|
||||
|
||||
await servicio.eliminarDispositivo(deviceId);
|
||||
|
||||
if (_dispositivoActualId == deviceId) {
|
||||
final resuelto = _resolverPresetActivo();
|
||||
_presetActual = resuelto;
|
||||
await audio.aplicarPreset(resuelto);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Returns the stored custom name for [deviceId], or an empty string if none.
|
||||
String obtenerNombreDispositivo(String deviceId) =>
|
||||
_nombresDispositivos[deviceId] ?? '';
|
||||
|
||||
Reference in New Issue
Block a user