fix(auto): advertise the transport actions Android for Cars requires
Reported: on the Android Auto playback screen the play/pause button stays
on PLAY while audio is audibly playing, and "it used to work, in the
latest versions it doesn't".
Previous rounds looked for a regression in this repo's audio commits and
found none: every playbackState.add site publishes playing:true with a
ready processingState, and AudioService.getPlaybackState maps that to
STATE_PLAYING. That search was aimed at the wrong thing.
The Android for Cars guide ("Enable playback control") is explicit:
"Android Auto and AAOS display playback controls based on the actions
that are enabled in the PlaybackStateCompat object. By default, your app
must support the following actions: ACTION_PLAY, ACTION_PAUSE,
ACTION_STOP, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH."
systemActions has carried only `seek` + `stop` since e9d1f67, the first
commit of the project -- git log -S confirms it was never once edited. So
the required actions have never been advertised, and no audio commit can
explain a change in behaviour. Android Auto ships as its own app and
updates itself, which is how a working screen breaks with a clean repo
history. That fits the report better than any commit here does.
The phone notification was never affected: it builds its play/pause
button from `controls`, not from these bits, which is exactly why the
symptom is car-only.
Skip actions stay conditional on an active queue on purpose -- the same
guide notes Auto reserves the prev/next slots for them and gives the
space to custom actions when the app does not support them, and that is
the space the equalizer toggle needs.
ACTION_PLAY_FROM_SEARCH is now implemented rather than merely claimed:
advertising it unimplemented would have the car's assistant accept "play
Radio X" and silently do nothing. emisoraParaBusqueda ranks exact name,
then prefix, then substring, then country, accent- and case-insensitive
because voice transcription rarely gets diacritics right; favourites are
searched first so they win a name tie, and a miss plays nothing rather
than something arbitrary.
Still a hypothesis for the play/pause symptom, not a confirmed fix -- it
is documentation-backed and cheap, but only a head unit can confirm it.
Tests: 1132 -> 1141.
This commit is contained in:
@@ -936,6 +936,59 @@ Future<void> reproducirPorMediaId(
|
||||
await reproducir(item);
|
||||
}
|
||||
|
||||
/// Picks the station a spoken query refers to ("pon Radio Clásica"), over the
|
||||
/// stations the car can already browse.
|
||||
///
|
||||
/// Pure and source-agnostic so it is testable without a handler. Ranking, best
|
||||
/// first:
|
||||
/// 1. exact name match (case/accent-insensitive),
|
||||
/// 2. name starts with the query,
|
||||
/// 3. name contains the query,
|
||||
/// 4. country contains the query.
|
||||
/// Ties are broken by the order [candidatas] arrives in, which the caller
|
||||
/// composes as favourites → my stations → all, so a favourite always wins over
|
||||
/// a stranger with the same name.
|
||||
///
|
||||
/// Returns `null` for an empty query or no match — the caller must then do
|
||||
/// nothing rather than play something arbitrary, since a driver who asked for
|
||||
/// a specific station is worse served by a random one than by silence.
|
||||
Emisora? emisoraParaBusqueda(String consulta, List<Emisora> candidatas) {
|
||||
final q = _normalizarBusqueda(consulta);
|
||||
if (q.isEmpty) return null;
|
||||
|
||||
Emisora? contiene;
|
||||
Emisora? empieza;
|
||||
Emisora? porPais;
|
||||
for (final emisora in candidatas) {
|
||||
final nombre = _normalizarBusqueda(emisora.nombre);
|
||||
if (nombre == q) return emisora;
|
||||
if (empieza == null && nombre.startsWith(q)) {
|
||||
empieza = emisora;
|
||||
} else if (contiene == null && nombre.contains(q)) {
|
||||
contiene = emisora;
|
||||
} else if (porPais == null &&
|
||||
_normalizarBusqueda(emisora.pais ?? '').contains(q)) {
|
||||
porPais = emisora;
|
||||
}
|
||||
}
|
||||
return empieza ?? contiene ?? porPais;
|
||||
}
|
||||
|
||||
/// Lowercase, accent-stripped, collapsed whitespace — a driver saying "radio
|
||||
/// clasica" must match "Radio Clásica", and voice transcription rarely gets
|
||||
/// diacritics right.
|
||||
String _normalizarBusqueda(String texto) {
|
||||
const conAcento = 'áàäâãéèëêíìïîóòöôõúùüûñç';
|
||||
const sinAcento = 'aaaaaeeeeiiiiooooouuuunc';
|
||||
final buffer = StringBuffer();
|
||||
for (final rune in texto.toLowerCase().runes) {
|
||||
final char = String.fromCharCode(rune);
|
||||
final i = conAcento.indexOf(char);
|
||||
buffer.write(i >= 0 ? sinAcento[i] : char);
|
||||
}
|
||||
return buffer.toString().replaceAll(RegExp(r'\s+'), ' ').trim();
|
||||
}
|
||||
|
||||
/// Routing seam for a car-tapped `eq_preset:<...>` media id (decision
|
||||
/// `auto/ecualizador-diseno`, mirrors [reproducirPorMediaId]'s seam
|
||||
/// shape): dispatches "Desactivar" to [activarEcualizador]`(false)`, and a
|
||||
|
||||
Reference in New Issue
Block a user