# Llevar PluriWave a Android Auto Guía para un desarrollador que **nunca publicó una app en Play Store** y quiere que PluriWave —instalada en el teléfono— se pueda **navegar y controlar desde la pantalla del coche** vía Android Auto. > **Alcance de esta guía.** Hablamos de **Android Auto proyectado**: la app corre en el > móvil y se proyecta al coche. NO es *Android Automotive OS* (donde la app se instala > dentro del sistema del vehículo). PluriWave es una app de audio → categoría > **"media app"**. Todo lo de abajo es para esa combinación. --- ## TL;DR (lo importante primero) 1. **El 60% ya está hecho.** PluriWave usa `audio_service`, que ya expone un `MediaBrowserService` + `MediaSession` (lo que Android Auto exige). No hay que reescribir el motor de audio. 2. **Falta lo que un coche necesita de más que un teléfono:** un **árbol navegable** de emisoras (para que el coche muestre una lista) y una **declaración en el manifest** para que Android Auto descubra la app. 3. **Trabajo real de código:** ~1 archivo XML nuevo + 1 línea en el manifest + implementar 3 métodos en `PluriWaveAudioHandler` (`getChildren`, `getMediaItem`, `playFromMediaId`). 4. **Publicación:** Android Auto añade una **revisión extra de Google** contra las *car app quality guidelines*. Es más estricta y más lenta que la de una app normal. Tiempo realista para un principiante: **1–2 semanas** (código un par de días, el resto es testing con el emulador de coche y la revisión de Google). --- ## Parte 0 — Cómo funciona (modelo mental) Un coche con Android Auto **no ejecuta tu UI de Flutter**. En su lugar, le pide a tu app dos cosas a través de un servicio estándar de Android: | El coche pregunta | Tu app responde | En Android esto es | |-------------------|-----------------|--------------------| | "¿Qué contenido tenés para mostrar?" | Una lista de ítems (carpetas + emisoras) | `MediaBrowserService` → `getChildren()` | | "El usuario tocó ESTE ítem, reproducilo" | Arrancás el stream | `MediaSession` → `playFromMediaId()` | | "Mostrame play/pausa/título/carátula" | El estado actual | `PlaybackState` + `MediaItem` | `audio_service` implementa el `MediaBrowserService` y el `MediaSession` por vos. Tu único trabajo es **rellenar las respuestas** (la lista de emisoras y cómo reproducir cada una). La UI del coche la dibuja **Android Auto**, no vos. Vos solo aportás datos y audio. --- ## Parte 1 — Qué ya tiene PluriWave (punto de partida) Verificado en el código actual: | Pieza | Dónde | Estado | |-------|-------|--------| | Dependencia `audio_service` `^0.18.15` | `pubspec.yaml` | ✅ | | `MediaBrowserService` declarado en el manifest | `android/app/src/main/AndroidManifest.xml:47-54` | ✅ | | `MediaButtonReceiver` (controles físicos/notificación) | `AndroidManifest.xml:62-68` | ✅ | | Inicialización del handler | `lib/main.dart:38` (`AudioService.init`) | ✅ | | Config del servicio | `lib/main.dart:21` (`AudioServiceConfig`) | ✅ | | Handler propio | `lib/servicios/servicio_audio.dart:127` (`PluriWaveAudioHandler extends BaseAudioHandler`) | ✅ | | Reproducir un ítem | `servicio_audio.dart:433` (`playMediaItem`) | ✅ | | Mapear `MediaItem` ↔ `Emisora` | `servicio_audio.dart:705` (`_emisoraDesdeMediaItem`) | ✅ | | `foregroundServiceType="mediaPlayback"` | `AndroidManifest.xml:49` | ✅ | **Ventaja clave de la versión 0.18:** el handler corre en el **mismo isolate** que la app, así que `getChildren()` puede leer directamente tu lista de emisoras/favoritos del estado de la app. No hay que sincronizar entre isolates. ### Lo que NO está (el hueco a rellenar) | Falta | Consecuencia hoy | |-------|------------------| | `res/xml/automotive_app_desc.xml` | Android Auto **no descubre** la app | | `` en el manifest | idem | | Override de `getChildren()` / `getMediaItem()` | El coche no tiene **ninguna lista** que mostrar | | Override de `playFromMediaId()` | Tocar una emisora en el coche **no reproduce** nada | | `MediaItem`s con carátula (`artUri`) por emisora | Google **rechaza** apps de media sin título+thumbnail por ítem | Hoy PluriWave solo sabe reproducir un `MediaItem` que le pasa **su propia UI de Flutter** (`playMediaItem`). El coche necesita el camino inverso: **pedir la lista** y **arrancar por id**. --- ## Parte 2 — Quick path (los pasos, en orden) ### Paso 1 · Declarar la app ante Android Auto Crear `android/app/src/main/res/xml/automotive_app_desc.xml`: ```xml ``` Añadir dentro de `` en `AndroidManifest.xml` (junto al resto de ``): ```xml ``` > Con esto Android Auto ya "ve" la app, pero seguirá vacía hasta el Paso 2. ### Paso 2 · Construir el árbol navegable (el trabajo de fondo) En `PluriWaveAudioHandler` (`lib/servicios/servicio_audio.dart`) implementar estos métodos. Firmas **verificadas** contra el código de `audio_service`: ```dart // Devuelve los hijos de una "carpeta". El root usa AudioService.browsableRootId. Future> getChildren(String parentMediaId, [Map? options]); // Metadatos de un ítem concreto (por si el coche los pide sueltos). Future getMediaItem(String mediaId); // El usuario tocó un ítem en la pantalla del coche → reproducirlo. Future playFromMediaId(String mediaId, [Map? extras]); // (Opcional) búsqueda por voz "pon Radio X". Future> playFromSearch(String query, [Map? extras]); ``` Diseño de árbol sugerido para PluriWave: ``` root (AudioService.browsableRootId) ├── Favoritos (playable: false → carpeta) │ ├── Emisora A (playable: true) │ └── Emisora B (playable: true) ├── Todas las emisoras (playable: false) │ └── ... └── Mis emisoras (playable: false) // las custom del usuario └── ... ``` Reglas de un `MediaItem`: | Campo | Carpeta | Emisora reproducible | |-------|---------|----------------------| | `id` | id estable de la categoría | id estable de la emisora | | `title` | nombre visible | nombre de la emisora **(obligatorio)** | | `playable` | `false` | `true` | | `artUri` | opcional | **carátula/logo (obligatorio para pasar la revisión)** | Lógica a reutilizar: ya tenés `_emisoraDesdeMediaItem` (`servicio_audio.dart:705`) y `playMediaItem` (`servicio_audio.dart:433`). En `playFromMediaId(id)` resolvés el id → `Emisora` → construís el `MediaItem` real → llamás al mismo `playMediaItem` interno. **No dupliques la lógica de reproducción**, enchufala. ### Paso 3 · Carátulas accesibles `artUri` tiene que ser una URL/҇URI que el sistema pueda cargar (http(s) o `content://`). Las emisoras que ya tienen logo remoto sirven directo. Para emisoras sin logo, definí una carátula por defecto (asset empaquetado servido vía `content://` o un placeholder remoto). ### Paso 4 · (Opcional pero recomendado) Content style Android Auto puede pintar los ítems como **lista** o **grid**. Se controla con hints en el `extras`/config del root (constantes `CONTENT_STYLE_*` de la spec de MediaBrowser). Para una app de radio, **grid** para emisoras (se ven los logos) queda mejor. Es pulido, no bloquea la publicación. --- ## Parte 3 · Probar sin coche (DHU — Desktop Head Unit) No necesitás un coche para testear. Google da un emulador de la pantalla del coche. **Quick path del testeo:** 1. En **Android Studio → SDK Manager → SDK Tools**, instalá **Android Auto Desktop Head Unit**. 2. En el **teléfono**: instalá la app *Android Auto*, entrá en sus ajustes y tocá 10 veces la versión para activar **modo desarrollador**; ahí activá **"Head unit server"**. 3. Conectá el teléfono por USB y lanzá el DHU: ```bash cd "$ANDROID_HOME/extras/google/auto" ./desktop-head-unit # (desktop-head-unit.exe en Windows) ``` 4. En la ventana del DHU deberías ver PluriWave en la sección de **media**. Navegá el árbol y reproducí una emisora. **Checklist de humo en el DHU:** - [ ] La app aparece en la lista de apps de media del coche. - [ ] Se ve el árbol (Favoritos / Todas / Mis emisoras). - [ ] Cada emisora muestra **título + carátula**. - [ ] Tocar una emisora **arranca el audio**. - [ ] Play / pausa / stop responden desde la pantalla del coche. - [ ] Al pausar en el coche, la app del teléfono refleja el mismo estado (y viceversa). --- ## Parte 4 · Publicar en Play Store (lo específico de un primer publicador) Publicar una app **con Android Auto** no es igual que una app normal: dispara una **revisión adicional** de Google contra las *car app quality guidelines*. ### Requisitos de calidad que Google verifica (media apps) | Requisito | Qué significa para PluriWave | |-----------|------------------------------| | Integración con MediaSession | Ya lo da `audio_service` ✅ | | Soportar play/pausa **o** stop | Ya lo tenés ✅ | | **Título + thumbnail por cada ítem** | ← esto es lo que hay que asegurar (Paso 3) | | Poder llegar a la vista de reproducción desde el browsing | Se cumple con el árbol bien armado | | **Al menos 1 screenshot real, sin editar, de la experiencia en coche** | Sacala del DHU | ### Pasos en Play Console 1. **Cuenta de desarrollador** (pago único de ~25 USD, primera vez). 2. Subí primero a un **track de pruebas cerrado**, NO directo a producción. > Importante: si el build va en un track de **testing** y no cumple, Google te avisa > pero **igual lo aprueba** para ese track. Si el mismo build va a **producción** y no > cumple, lo **rechaza**. Por eso: cerrado → arreglás → producción. 3. Completá la **ficha de la tienda** + el cuestionario de contenido/privacidad (obligatorio para cualquier app nueva). 4. Subí la **screenshot de la experiencia en coche** (del DHU). 5. Enviá a revisión. La revisión de coche puede tardar **de unas horas hasta 7 días** (a veces más), bastante más que una app solo-móvil. ### Gotchas para PluriWave concretamente - **Muchos permisos sensibles.** El manifest pide localización, `RECORD_AUDIO`, `SCHEDULE_EXACT_ALARM`, `SYSTEM_EXEMPTED`, etc. La revisión de coche mira con lupa; tené a mano la justificación de cada permiso (la sección de *foreground service* de Play Console te va a pedir el porqué de `mediaPlayback`). - **`targetSdk`** sale de `flutter.targetSdkVersion` (`android/app/build.gradle`). Play exige un target reciente para apps nuevas; verificá que cumpla el mínimo del año antes de subir. - **Streams que fallan.** Google prueba reproducir. Si una emisora del árbol está caída, da mala impresión. Exponé en el árbol emisoras fiables (favoritos del usuario, o un set curado) y manejá el error de stream con gracia (ya tenés `controlador_reconexion.dart`). --- ## Checklist maestro **Código** - [ ] `res/xml/automotive_app_desc.xml` creado (``). - [ ] `` en el manifest. - [ ] `getChildren()` devuelve el árbol (carpetas + emisoras). - [ ] `getMediaItem()` resuelve un id suelto. - [ ] `playFromMediaId()` reutiliza `playMediaItem` interno. - [ ] Cada emisora expone `title` + `artUri`. **Testing** - [ ] Funciona en el DHU (navegar + reproducir + play/pausa). - [ ] Estado sincronizado coche ↔ teléfono. **Publicación** - [ ] Cuenta de desarrollador creada. - [ ] Screenshot de la experiencia en coche subida. - [ ] Subido primero a track cerrado. - [ ] Justificación de permisos preparada. - [ ] Enviado a revisión. --- ## Referencias - [Media apps for cars — overview (Android Developers)](https://developer.android.com/training/cars/media) - [Add support for Android Auto to your media app](https://developer.android.com/training/cars/media/auto) - [Car app quality guidelines](https://developer.android.com/docs/quality-guidelines/car-app-quality) - [Distribute to cars (Play Console)](https://developer.android.com/training/cars/distribute) - [audio_service (pub.dev)](https://pub.dev/packages/audio_service) - [audio_service — repo y ejemplos (GitHub)](https://github.com/ryanheise/audio_service) --- ## Próximo paso sugerido Empezar por el **Paso 1 + Paso 2 con un árbol mínimo** (solo "Favoritos" con 2–3 emisoras hardcodeadas) y verlo en el **DHU**. Cuando eso reproduzca en el emulador de coche, recién ahí ampliar el árbol y pulir carátulas. Es el bucle de feedback más corto para no programar a ciegas.