feat(eq): name Bluetooth devices from the system pairing list
A Bluetooth device only reports its own name through AudioDeviceInfo.productName while it is enumerated as an active output, i.e. while it is connected. Paired-but-switched-off devices therefore had no name to fall back on, and the platform-name cache is in-memory only by design (bt-device-identity ADR-4), so it self-heals per session ONLY for whatever happens to be connected. Every other device showed its raw id. Android already knows those names: BluetoothAdapter.getBondedDevices() lists every pairing with its name and MAC, connected or not, and nothing in this app was asking. Read it and seed the platform-name cache from it, keyed bt_a2dp:<uppercase MAC> to match the ids the audio layer emits. Seeded BEFORE the active-device query so a live enumeration name, being the fresher of the two, still wins; a user's custom name outranks both. Re-read on refrescarDispositivoActual so pairing or renaming a device in system settings shows up as soon as the list becomes visible. Reading the bond list is gated by BLUETOOTH_CONNECT from API 31 and by the legacy BLUETOOTH permission below it, so declare the latter with maxSdkVersion 30. It is a normal permission: granted at install, no runtime prompt, no new friction. When the answer is unavailable — permission denied, no adapter, Bluetooth off — both layers return an empty map rather than throwing, and the row degrades to the id exactly as before. Does not help rows persisted under a bt_a2dp:name: placeholder id: those never had a MAC to match against.
This commit is contained in:
@@ -1543,6 +1543,93 @@ void main() {
|
||||
// builtin_speaker id collision + device removal
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
group('EstadoEcualizador — bonded Bluetooth names', () {
|
||||
const deviceId = 'bt_a2dp:AA:BB:CC:DD:EE:FF';
|
||||
|
||||
test('a paired but disconnected device resolves its system name', () async {
|
||||
final fakeDispositivo = FakeServicioDispositivoAudio(
|
||||
nombresEmparejados: {'AA:BB:CC:DD:EE:FF': 'Omoda'},
|
||||
);
|
||||
final eq = EstadoEcualizador(
|
||||
audio: FakeServicioAudio(),
|
||||
servicio: FakeServicioEcualizador(
|
||||
eqMultiDeviceEnabled: true,
|
||||
presetsDispositivo: {deviceId: PresetEcualizador.rock},
|
||||
),
|
||||
dispositivoAudio: fakeDispositivo,
|
||||
emisoraActualUuid: () => null,
|
||||
);
|
||||
|
||||
await eq.cargarPersistido();
|
||||
|
||||
// The device was never seen on the stream this session, so the only
|
||||
// possible source is the system's bonded-device list.
|
||||
expect(eq.nombrePlataforma(deviceId), equals('Omoda'));
|
||||
eq.dispose();
|
||||
});
|
||||
|
||||
test('a live enumeration name wins over the bonded one', () async {
|
||||
final fakeDispositivo = FakeServicioDispositivoAudio(
|
||||
nombresEmparejados: {'AA:BB:CC:DD:EE:FF': 'Stale pairing name'},
|
||||
);
|
||||
final eq = EstadoEcualizador(
|
||||
audio: FakeServicioAudio(),
|
||||
servicio: FakeServicioEcualizador(eqMultiDeviceEnabled: true),
|
||||
dispositivoAudio: fakeDispositivo,
|
||||
emisoraActualUuid: () => null,
|
||||
);
|
||||
await eq.cargarPersistido();
|
||||
|
||||
fakeDispositivo.emitirDispositivo(
|
||||
const DispositivoAudio(
|
||||
id: deviceId,
|
||||
tipo: TipoDispositivo.bluetoothA2dp,
|
||||
nombre: 'Omoda',
|
||||
),
|
||||
);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(eq.nombrePlataforma(deviceId), equals('Omoda'));
|
||||
eq.dispose();
|
||||
});
|
||||
|
||||
test('a custom name still beats the bonded one', () async {
|
||||
final fakeDispositivo = FakeServicioDispositivoAudio(
|
||||
nombresEmparejados: {'AA:BB:CC:DD:EE:FF': 'OMODA 5'},
|
||||
);
|
||||
final eq = EstadoEcualizador(
|
||||
audio: FakeServicioAudio(),
|
||||
servicio: FakeServicioEcualizador(
|
||||
eqMultiDeviceEnabled: true,
|
||||
nombresDispositivos: {deviceId: 'Coche'},
|
||||
),
|
||||
dispositivoAudio: fakeDispositivo,
|
||||
emisoraActualUuid: () => null,
|
||||
);
|
||||
await eq.cargarPersistido();
|
||||
|
||||
expect(
|
||||
eq.nombreVisible(deviceId, eq.nombrePlataforma(deviceId)),
|
||||
equals('Coche'),
|
||||
);
|
||||
eq.dispose();
|
||||
});
|
||||
|
||||
test('a failing bonded-name lookup never breaks startup', () async {
|
||||
final eq = EstadoEcualizador(
|
||||
audio: FakeServicioAudio(),
|
||||
servicio: FakeServicioEcualizador(eqMultiDeviceEnabled: true),
|
||||
dispositivoAudio: FakeServicioDispositivoAudioThrows(),
|
||||
emisoraActualUuid: () => null,
|
||||
);
|
||||
|
||||
await eq.cargarPersistido();
|
||||
|
||||
expect(eq.nombrePlataforma('bt_a2dp:AA:BB'), isEmpty);
|
||||
eq.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
group('EstadoEcualizador — builtin_speaker collision guard', () {
|
||||
test(
|
||||
'an unknown-type device reported under the base id creates no entry',
|
||||
|
||||
Reference in New Issue
Block a user