Cómo compilar y probar una app para usar en el SO Android Automotive con el automóvil estacionado: Admite audio mientras conduces

1. Antes de comenzar

Qué no es este codelab

Requisitos

Qué compilarás

En este codelab, aprenderás a agregar compatibilidad con audio mientras conduces en Road Reels, una app existente que admite dispositivos móviles y el SO Android Automotive.

Es la versión inicial de la pausa de la reproducción mientras las restricciones de la experiencia del usuario están activas.

La versión completa de la app continúa la reproducción mientras las restricciones de la experiencia del usuario están activas.

Qué aprenderás

  • Cómo habilitar la reproducción de audio en segundo plano en una app de video en el SO Android Automotive

2. Prepárate

Obtén el código

  1. El código para este codelab se puede encontrar en el directorio build-a-parked-app dentro del repositorio car-codelabs de GitHub. Para clonarlo, ejecuta el siguiente comando:
git clone https://github.com/android/car-codelabs.git
  1. También tienes la opción de descargar el repositorio como archivo ZIP:

Abre el proyecto

  • Después de iniciar Android Studio, importa el proyecto. Elige solamente el directorio build-a-parked-app/end. El directorio build-a-parked-app/audio-while-driving contiene el código de la solución, que puedes consultar en cualquier momento si no logras avanzar o si deseas ver el proyecto completo.

Familiarízate con el código

  • Después de abrir el proyecto en Android Studio, dedica un momento a analizar el código de partida.

3. Aprende sobre las apps para usar en el SO Android Automotive con el automóvil estacionado

Las apps destinadas a usarse con el automóvil estacionado conforman un subconjunto de las categorías de apps compatibles con el SO Android Automotive. Al momento de escribir, constan de apps de transmisión de video por Internet, navegadores web y juegos. Estas apps son ideales para automóviles debido al hardware de los automóviles con Google integrado y a la prevalencia en constante crecimiento de los automóviles eléctricos, en los que el tiempo de carga representa una gran oportunidad para que los conductores y los pasajeros interactúen con este tipo de apps.

En muchos sentidos, los automóviles son similares a otros dispositivos con pantalla grande, como las tablets y los dispositivos plegables. Tienen pantallas táctiles con tamaños, resoluciones y relaciones de aspecto similares y que pueden estar en orientación horizontal o vertical (aunque, a diferencia de las tablets, su orientación es fija). También son dispositivos conectados que pueden entrar en la conexión de red y salir de esta. Con todo esto en mente, no sorprende que las apps que ya están optimizadas para pantallas grandes no suelan requerir mucho trabajo para ofrecer una excelente experiencia del usuario en automóviles.

Al igual que para las pantallas grandes, también hay niveles de calidad de las apps para automóviles:

  • Nivel 3 (lista para el automóvil): Tu app es compatible con pantallas grandes y se puede usar mientras el automóvil está estacionado. Si bien es posible que no tenga ninguna función optimizada para automóviles, los usuarios pueden experimentar la app como lo harían en cualquier otro dispositivo Android con pantalla grande. Las apps para dispositivos móviles que cumplen con estos requisitos son aptas para distribuirse en automóviles sin modificaciones a través del programa de apps para dispositivos móviles listas para usar en automóviles.
  • Nivel 2 (optimizada para el automóvil): Tu app brinda una gran experiencia en la pantalla central del automóvil. Para ello, tendrá ingeniería específica del automóvil para incluir capacidades que se pueden usar en los modos de conducción o estacionamiento, según la categoría de tu app.
  • Nivel 1 (diferenciada para el automóvil): Tu app se diseñó para funcionar con una amplia variedad de hardware en automóviles y puede adaptar su experiencia a los modos de conducción y estacionamiento. Proporciona la mejor experiencia del usuario diseñada para las diferentes pantallas de los automóviles, como la consola central, el clúster de instrumentos y las pantallas adicionales, como las panorámicas que se encuentran en muchos automóviles de alta gama.

En este codelab, implementarás la reproducción de audio mientras conduces, que es una función de nivel 1, lo que hace que la app sea diferenciada para automóviles.

4. Ejecuta la app en el emulador del SO Android Automotive

Instala Automotive con las imágenes del sistema de Play Store

  1. Primero, abre SDK Manager en Android Studio y selecciona la pestaña SDK Platforms si aún no está seleccionada. En la esquina inferior derecha de la ventana de SDK Manager, asegúrate de que esté marcada la casilla junto a Show package details.
  2. Instala una de las imágenes del emulador Android Automotive del nivel de API 34 que se indican en Add generic system images. Las imágenes solo se pueden ejecutar en máquinas que tienen su misma arquitectura (x86/ARM).

Crea un dispositivo virtual del SO Android Automotive

  1. Después de abrir el Administrador de dispositivos, selecciona Automotive debajo de la columna Category en la parte izquierda de la ventana. Luego, selecciona el perfil de hardware incluido Automotive (1408p landscape) de la lista y haz clic en Next.

6a32a01404a7729f.png

  1. En la siguiente página, selecciona la imagen del sistema del paso anterior. Haz clic en Next y selecciona las opciones avanzadas que desees antes de crear el AVD haciendo clic en Finish.

Ejecuta la app

Ejecuta la app en el emulador que acabas de crear usando la configuración de ejecución app. Para probar el comportamiento de la app, navega a la pantalla del reproductor y simula la conducción.

5. Detecta la compatibilidad con audio mientras conduces

Como el audio mientras se conduce no es compatible con todos los vehículos, deberás detectar si el dispositivo actual admite la función y adaptar el comportamiento de tu app según corresponda. Para ello, puedes usar la clase CarFeatures de la biblioteca androidx.car.app:app.

  1. Agrega una dependencia en el artefacto androidx.car.app:app.

libs.version.toml

[versions]
...
carApp = "1.7.0-rc01"


[libraries]
...
androidx-car-app = { group = "androidx.car.app", name = "app", version.ref = "carApp" }

build.gradle.kts (módulo :app)

implementation(libs.androidx.car.app)
  1. En RoadReelsPlayer, puedes determinar si la función es compatible y actualizar la lógica que se usa para calcular el valor de shouldPreventPlay.

RoadReelsPlayer.kt

if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
    ...

    val isBackgroundAudioWhileDrivingSupported = CarFeatures.isFeatureEnabled(
        context,
        CarFeatures.FEATURE_BACKGROUND_AUDIO_WHILE_DRIVING
    )

    shouldPreventPlay = !isBackgroundAudioWhileDrivingSupported &&
            carUxRestrictionsManager.currentCarUxRestrictions.isRequiresDistractionOptimization
    invalidateState()

    carUxRestrictionsManager.registerListener { carUxRestrictions: CarUxRestrictions ->
        shouldPreventPlay = !isBackgroundAudioWhileDrivingSupported &&
            carUxRestrictions.isRequiresDistractionOptimization
        ...
    }
}

Vuelve a ejecutar la app con estos cambios y simula la conducción. Observa que la reproducción de audio ahora continúa cuando el sistema oculta la IU de la app. Sin embargo, aún no terminaste. Para cumplir con todos los requisitos, deberás realizar algunos cambios más.

6. Cómo admitir la reproducción en segundo plano

Actualmente, una actividad controla la reproducción de contenido multimedia de la app. Por lo tanto, la reproducción de contenido multimedia puede continuar durante un período breve después de que las restricciones de la experiencia del usuario se activen y la actividad se oculte, pero el sistema eventualmente almacenará en caché tu app, lo que hará que finalice la reproducción.

Para admitir la reproducción de larga duración, la app debe actualizarse para usar un servicio que controle la reproducción. Esto se puede lograr con MediaSessionService de Media3.

Cómo crear un MediaSessionService

  1. Haz clic con el botón derecho en el paquete com.example.android.cars.roadreels en la ventana Project y selecciona New > Kotlin Class/File. Ingresa PlaybackService como nombre del archivo y haz clic en el tipo Class.
  2. Agrega la siguiente implementación de PlaybackService.. Consulta Cómo reproducir contenido en segundo plano con un MediaSessionService para obtener más información sobre MediaSessionService.

PlaybackService.kt

import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService

@UnstableApi
class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    override fun onCreate() {
        super.onCreate()
        val player = RoadReelsPlayer(this)
        mediaSession = MediaSession.Builder(this, player).build()
    }

    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
        if (controllerInfo.isTrusted || controllerInfo.packageName == packageName) {
            return mediaSession
        }
        return null
    }
}
  1. Agrega los siguientes elementos <uses-permission> en el archivo de manifiesto. La reproducción de contenido multimedia se controla con un servicio en primer plano

AndroidManifest.xml

<manifest ...>
    ...
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
    ...
</manifest>
  1. Declara también PlaybackService en el manifiesto:

AndroidManifest.xml

<application ...>
    ...
    <service
        android:name=".PlaybackService"
        android:foregroundServiceType="mediaPlayback"
        android:exported="true">
        <intent-filter>
            <action android:name="androidx.media3.session.MediaSessionService"/>
        </intent-filter>
    </service>
</application>

Actualiza PlayerViewModel para usar PlaybackService

  1. Debido a que PlaybackService crea y expone un MediaSession, ya no es necesario que PlayerViewModel cree uno. Busca y borra la siguiente línea y todas las referencias a la variable:

PlayerViewModel.kt

private var mediaSession: MediaSession? = null
  1. A continuación, reemplaza la sección del bloque init que crea una instancia de RoadReelsPlayer por el siguiente código que conecta la app a PlaybackService con un MediaController.

PlayerViewModel.kt

import android.content.ComponentName
import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import com.example.android.cars.roadreels.PlaybackService
import com.google.common.util.concurrent.MoreExecutors

...

init {
        viewModelScope.launch {
            ...
        }
        
        val sessionToken = SessionToken(
            application,
            ComponentName(application, PlaybackService::class.java)
        )

        val mediaControllerFuture =
            MediaController.Builder(getApplication(), sessionToken).buildAsync()

        mediaControllerFuture.addListener(
            { _player.update { mediaControllerFuture.get() } },
            MoreExecutors.directExecutor()
        )
    }

Vuelve a probar la app. Deberías ver que aparece una IU diferente cuando el sistema bloquea la app. Ahora, el usuario puede controlar la reproducción mientras está en movimiento. Cuando vuelva a estar en reposo, podrá hacer clic en el botón de salida para volver a la experiencia en la app completa.

33eb8ff3d4035978.gif

Cómo quitar los cambios de reproducción del ciclo de vida

Como ahora se admite la reproducción en segundo plano, puedes quitar los dos bloques LifecycleEventEffect en PlayerScreen.kt para que la reproducción pueda continuar cuando el usuario salga de la app, lo que incluye cuando se muestran los controles de reproducción de la pantalla anterior.

Cómo pausar la reproducción cuando sales de la pantalla del reproductor

Debido a que el reproductor real (que ahora se encuentra dentro de PlaybackService) no se libera cuando sales de la pantalla del reproductor, deberás agregar una llamada para pausar la reproducción cuando salgas de ella para mantener el comportamiento anterior. Para ello, puedes actualizar la implementación del método onCleared de PlayerViewModel:

PlayerViewModel.kt

override fun onCleared() {
    super.onCleared()
    _player.value?.apply {
        pause()
        release()
    }
}

7. Actualiza el manifiesto

Por último, para indicar que tu app admite audio mientras se conduce, debes agregar el siguiente elemento <uses-feature> al manifiesto de la app.

AndroidManifest.xml

<application>
    <uses-feature android:name="com.android.car.background_audio_while_driving" android:required="false">
</application>

8. Felicitaciones

Agregaste correctamente la compatibilidad con audio mientras se conduce a una app de video. Ahora es momento de aplicar lo que aprendiste a tu propia app.

Lecturas adicionales