Apprendre les principes de base de la bibliothèque Car App

1. Avant de commencer

Dans cet atelier de programmation, vous allez apprendre à créer des applications optimisées contre la distraction pour Android Auto et Android Automotive OS à l'aide de la bibliothèque d'applications Android for Cars. Vous commencerez par ajouter la compatibilité avec Android Auto puis créerez facilement une variante de l'application pouvant s'exécuter sur Android Automotive OS. Après avoir configuré l'application pour qu'elle fonctionne sur les deux plates-formes, vous ajouterez un écran supplémentaire et quelques interactivités de base !

Prérequis

Objectifs de l'atelier

Android Auto

Android Automotive OS

Un enregistrement d'écran montrant l'application qui s'exécute sur Android Auto grâce à l'unité principale pour ordinateur.

Un enregistrement d'écran montrant l'application qui s'exécute sur un émulateur d'Android Automotive OS.

Points abordés

  • Fonctionnement de l'architecture client-hôte de la bibliothèque Car App.
  • Création de vos propres classes CarAppService, Session et Screen.
  • Partage de votre implémentation sur Android Auto et Android Automotive OS.
  • Exécution d'Android Auto sur votre ordinateur de développement à l'aide de l'unité principale pour ordinateur.
  • Utilisation de l'émulateur Android Automotive OS.

2. Configuration

Obtenir le code

  1. Le code pour cet atelier de programmation se trouve dans le répertoire car-app-library-fundamentals au sein du dépôt GitHub car-codelabs. Pour le cloner, exécutez la commande suivante :
git clone https://github.com/android/car-codelabs.git
  1. Vous pouvez aussi télécharger le dépôt sous la forme d'un fichier ZIP :

Télécharger le fichier ZIP

Ouvrir le projet

  • Après avoir démarré Android Studio, importez le projet en sélectionnant uniquement le répertoire car-app-library-fundamentals/start. Le répertoire car-app-library-fundamentals/end contient le code de solution, que vous pouvez consulter à tout moment en cas de difficulté ou pour avoir un aperçu du projet dans son ensemble.

Vous familiariser avec le code

  • Après avoir ouvert le projet dans Android Studio, prenez le temps d'examiner le code de démarrage.

Notez que le code de démarrage pour l'application est divisé en deux modules, :app et :common:data.

Le module :app dépend du module :common:data.

Le module :app contient l'interface utilisateur et la logique de l'application mobile, tandis que le module :common:data contient la classe de données et le dépôt du modèle Place, utilisé pour lire les modèles Place. En résumé, le dépôt lit une liste codée en dur, mais il pourrait facilement lire une base de données ou un serveur backend dans une application réelle.

Le module :app comprend une dépendance au module :common:data lui permettant de lire et de présenter la liste des modèles Place.

3. En savoir plus sur la bibliothèque d'applications Android for Cars

La bibliothèque d'applications Android for Cars est un ensemble de bibliothèques Jetpack qui permettent aux développeurs de créer des applications pour les voitures. Elle offre un modèle de framework qui fournit des interfaces utilisateur optimisées pour la conduite tout en s'adaptant aux différentes configurations matérielles présentes dans les voitures (modes de saisie, tailles d'écran, formats, etc.). Cela vous permet, en tant que développeur, de créer facilement une application et d'avoir la certitude qu'elle fonctionnera correctement sur plusieurs véhicules équipés d'Android Auto ou d'Android Automotive OS.

En savoir plus sur le fonctionnement

Les applications créées à l'aide de la bibliothèque Car App ne s'exécutent pas directement sur Android Auto ou Android Automotive OS. Elles s'appuient plutôt sur une application hôte qui communique avec les applications clientes (votre application) et affiche les interfaces utilisateur de ces dernières en leur nom. Android Auto est, elle-même, une hôte et Google Automotive App Host est l'hôte utilisé sur les véhicules équipés d'Android Automotive OS avec Google intégré.

Voici les principales classes de la bibliothèque Car App que vous devez étendre lors de la création de votre application :

CarAppService

CarAppService est une sous-classe de la classe Service d'Android et sert de point d'entrée pour la communication entre les applications hôtes et les applications clientes (comme celle que vous allez créer dans cet atelier). Son rôle principal est de créer des instances Session avec lesquelles l'application hôte interagit.

Session

Une instance Session est une instance d'une application cliente s'exécutant sur l'écran d'un véhicule. Comme d'autres composants Android, elle a son propre cycle de vie qui peut être utilisé pour initialiser et supprimer des ressources utilisées par l'instance Session. Il existe une relation d'un à plusieurs entre CarAppService et Session. Par exemple, un CarAppService peut avoir deux instances Session, une pour un premier écran et l'autre pour un écran de cluster pour les applications de navigation compatibles avec les écrans de cluster.

Écran

Les instances Screen sont responsables de la génération d'interfaces utilisateur affichées par les applications hôtes. Ces interfaces utilisateur sont représentées par des classes Template et chacune de ces classes modélise une certaine mise en page, comme une grille ou une liste. Chaque Session gère une pile d'instances Screen qui gèrent les parcours utilisateurs à travers les différentes parties de votre application. Comme pour une instance Session, Screen a son propre cycle de vie auquel vous pouvez vous connecter.

Un schéma sur le fonctionnement de la bibliothèque Car App. À gauche, deux cases intitulées "Display" (Affichage). Au centre, une case "Host" (Hôte). À droite, une case "CarAppService". Cette dernière est sous-divisée en deux cases intitulées "Session". Dans la première case "Session" se trouvent trois cases "Screen" (Écran) superposées. Dans la seconde case "Session" se trouvent deux cases "Screen" (Écran) superposées. Des flèches relient chaque case "Display" (Affichage) à la case "Host" (Hôte), elle-même reliée par des flèches à chaque case "Session" afin d'indiquer comment l'hôte gère la communication entre les différents composants.

Vous découvrirez tout ce que vous avez besoin de savoir sur ces classes lorsque vous écrirez un CarAppService, un Session et un Screen dans la section Write your CarAppService (Créer votre CarAppService) de cet atelier de programmation.

4. Configuration initiale

Pour commencer, configurez le module contenant le CarAppService et déclarez ses dépendances.

Créer le module car-app-service

  1. Sélectionnez le module :common dans la fenêtre Project (Projet), faites un clic droit et sélectionnez l'option New > Module (Nouveau > Module).
  2. L'assistant du module s'ouvre. Dans la liste se trouvant dans le volet de gauche, sélectionnez le modèle Android Library (bibliothèque Android), de façon à ce que ce module puisse être utilisé comme une dépendance par d'autres modules. Définissez ensuite les valeurs suivantes :
  • Module name (nom du module) : :common:car-app-service
  • Package name (nom du package) : com.example.places.carappservice
  • Minimum SDK (SDK minimum) : API 23: Android 6.0 (Marshmallow)

Assistant "Create New Module" (Créer un nouveau module) avec les valeurs décrites ci-dessus.

Configurer des dépendances

  1. Dans le fichier libs.version.toml, ajoutez des entrées pour l'artefact 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" }
  1. Ensuite, ajoutez deux dépendances au fichier build.gradle.kts du module :common:car-app-service.
  • androidx.car.app:app. Il s'agit de l'artefact principal de la bibliothèque Car App, qui comprend toutes les classes de base utilisées lors de la création d'applications. La bibliothèque se compose de trois autres artefacts : androidx.car.app:app-projected pour les fonctionnalités propres à Android Auto, androidx.car.app:app-automotive pour le code des fonctionnalités Android Automotive OS, et androidx.car.app:app-testing pour certains assistants utiles lors des tests unitaires. Vous utiliserez app-projected et app-automotive plus tard dans cet atelier.
  • :common:data. Il s'agit du même module de données que celui utilisé par l'application mobile existante et il permet d'utiliser les mêmes sources de données pour chaque version de l'expérience dans l'application.

build.gradle.kts (Module :common:car-app-service)

dependencies {
    ...
    implementation(libs.androidx.car.app)
    implementation(project(":common:data"))
    ...
}

Avec ce changement, le graphique de dépendance pour les modules propres à l'application est le suivant :

Les modules :app et :common:car-app-service dépendent tous les deux du module :common:data.

Maintenant que vous avez configuré les dépendances, passons à l'écriture de CarAppService.

5. Écrire votre CarAppService

  1. Commencez par créer un fichier intitulé PlacesCarAppService.kt dans le package carappservice à l'intérieur du module :common:car-app-service.
  2. Dans ce fichier, créez une classe intitulée PlacesCarAppService, qui étend CarAppService.

PlacesCarAppService.kt

import androidx.car.app.CarAppService
import androidx.car.app.Session
import androidx.car.app.SessionInfo
import androidx.car.app.validation.HostValidator

...

class PlacesCarAppService : CarAppService() {

    override fun createHostValidator(): HostValidator {
        return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
    }

   override fun onCreateSession(sessionInfo: SessionInfo): Session {
        // PlacesSession will be an unresolved reference until the next step
        return PlacesSession()
    }
}

La classe abstraite CarAppService implémente les méthodes Service telles que onBind et onUnbind pour vous et empêche les futurs forçages de ces méthodes afin d'assurer une bonne interopérabilité avec les applications hôte. Il vous suffit d'implémenter createHostValidator et onCreateSession.

Le HostValidator que vous renvoyez à partir de createHostValidator est référencé lors de la liaison de votre CarAppService afin de s'assurer que l'hôte est fiable et que la liaison échoue si l'hôte ne correspond pas aux paramètres que vous avez définis. Pour les besoins de cet atelier de programmation (et des tests en général), l'ALLOW_ALL_HOSTS_VALIDATOR permet de s'assurer facilement que votre application se connecte, mais il ne doit pas être utilisé en production. Consultez la documentation sur createHostValidator afin d'en savoir plus sur sa configuration pour une application de productivité.

Pour une application aussi simple que celle-ci, onCreateSession peut simplement renvoyer une instance d'une Session. Dans une application plus complexe, nous aurions pu initialiser les ressources à long terme telles que les métriques et les clients de journalisation qui sont utilisés pendant que votre application s'exécute sur le véhicule.

  1. Enfin, vous devez ajouter l'élément <service> qui correspond au PlacesCarAppService dans le fichier AndroidManifest.xml du module :common:car-app-service afin que le système d'exploitation (et, par extension, d'autres applications telles que les applications hôtes) sache que cet élément existe.

AndroidManifest.xml (:common:car-app-service)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!--
        This AndroidManifest.xml will contain all of the elements that should be shared across the
        Android Auto and Automotive OS versions of the app, such as the CarAppService <service> element
    -->

    <application>
        <service
            android:name="com.example.places.carappservice.PlacesCarAppService"
            android:exported="true">
            <intent-filter>
                <action android:name="androidx.car.app.CarAppService" />
                <category android:name="androidx.car.app.category.POI" />
            </intent-filter>
        </service>
    </application>
</manifest>

Il y a deux choses importantes à retenir ici :

  • L'élément <action> permet aux applications hôtes (et au lanceur d'applications) de trouver l'application.
  • L'élément <category> déclare la catégorie de l'application (point d'intérêt dans ce cas), qui détermine les critères de qualité auxquels l'application doit répondre (nous y reviendrons plus tard). Les autres valeurs possibles sont détaillées dans Supported app categories (Catégories d'applications compatibles).

Créer la classe PlacesSession

  • Dans PlacesCarAppService.kt, ajoutez le code suivant :

PlacesCarAppService.kt

import android.content.Intent
import androidx.car.app.Screen

...

class PlacesSession : Session() {
    override fun onCreateScreen(intent: Intent): Screen {
        // MainScreen will be an unresolved reference until the next step
        return MainScreen(carContext)
    }
}

Pour une application simple comme celle-ci, vous pouvez renvoyer l'écran principal dans onCreateScreen. Cependant, comme cette méthode prend un Intent comme paramètre, une application avec plus de fonctionnalités pourrait également le lire et insérer une pile "Retour" d'écrans ou utiliser d'autres logiques conditionnelles.

Créer la classe MainScreen

Ensuite, créez un nouveau package intitulé screen.

  1. Effectuez un clic droit sur le package com.example.places.carappservice et sélectionnez New > Package (Nouveau > Package), le nom complet du package sera alors com.example.places.carappservice.screen. C'est à cet endroit que vous mettez toutes les sous-classes Screen de l'application.
  2. Dans le package screen, créez un fichier intitulé MainScreen.kt pour contenir la classe MainScreen, qui étend Screen. Pour le moment, un simple message Hello, world! grâce au PaneTemplate.

MainScreen.kt

import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.Header
import androidx.car.app.model.Pane
import androidx.car.app.model.PaneTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.Template

...

class MainScreen(carContext: CarContext) : Screen(carContext) {

    override fun onGetTemplate(): Template {
        val row = Row.Builder()
            .setTitle("Hello, world!")
            .build()

        val pane = Pane.Builder()
            .addRow(row)
            .build()

        return PaneTemplate.Builder(pane)
            .setHeader(
                Header.Builder()
                    .setStartHeaderAction(Action.APP_ICON)
                    .build()
            ).build()
    }
}

6. Ajouter la prise en charge d'Android Auto

Bien que vous ayez maintenant implémenté toute la logique nécessaire pour que l'application soit opérationnelle, il vous reste deux éléments de configuration à mettre en place avant de pouvoir l'exécuter sur Android Auto.

Ajouter une dépendance au module car-app-service

Dans le fichier build.gradle.kts du module :app, ajoutez ceci :

build.gradle.kts (Module :app)

dependencies {
    ...
    implementation(project(":common:car-app-service"))
    ...
}

Avec ce changement, le graphique de dépendance pour les modules propres à l'application est le suivant :

Les modules :app et :common:car-app-service dépendent tous les deux du module :common:data. Le module :app dépend aussi du module :common:car-app-service.

Cela regroupe le code que vous venez d'écrire dans le module :common:car-app-service avec d'autres composants inclus dans la bibliothèque Car App, tels que l'autorisation accordée autorisant l'activité.

Déclarer les métadonnées com.google.android.gms.car.application

  1. Effectuez un clic droit sur le module :common:car-app-service et sélectionnez l'option New > Android Resource File (Nouveau > Fichier de ressources Android), puis sélectionnez les valeurs suivantes :
  • File name (Nom du fichier) : automotive_app_desc.xml
  • Resource type (Type de ressource) : XML
  • Root element (Élément racine) : automotiveApp

Assistant &quot;New Resource File&quot; (Nouveau fichier de ressources) avec les valeurs décrites ci-dessus.

  1. Dans ce fichier, ajoutez l'élément <uses> afin de déclarer que votre application utilise les modèles fournis par la bibliothèque Car App.

automotive_app_desc.xml

<?xml version="1.0" encoding="utf-8"?>
<automotiveApp>
    <uses name="template"/>
</automotiveApp>
  1. Dans le fichier AndroidManifest.xml du module :app, ajoutez l'élément suivant <meta-data> qui référence le fichier automotive_app_desc.xml que vous venez de créer.

AndroidManifest.xml (:app)

<application ...>

    <meta-data
        android:name="com.google.android.gms.car.application"
        android:resource="@xml/automotive_app_desc" />

    ...

</application>

Ce fichier est lu par Android Auto et lui indique les capacités prises en charge par votre application. Dans notre cas, il indique qu'elle utilise le système de modèles de la bibliothèque Car App. Cette information est ensuite utilisée pour gérer différents comportements, comme l'ajout de l'application au lanceur d'Android Auto et son ouverture à partir des notifications.

Facultatif : écouter les modifications de projection

Vous souhaitez parfois savoir si l'appareil d'un utilisateur est connecté ou non à un véhicule. Pour ce faire, utilisez l'API CarConnection, qui fournit des LiveData vous permettant d'observer l'état de connexion de l'appareil.

  1. Pour utiliser l'API CarConnection, commencez par ajouter une dépendance au module :app dans l'artefact androidx.car.app:app.

build.gradle.kts (Module :app)

dependencies {
    ...
    implementation(libs.androidx.car.app)
    ...
}
  1. À des fins de démonstration, vous pouvez ensuite créer un composable simple, comme le suivant, qui affiche l'état de connexion actuel. Dans une application réelle, cet état peut être enregistré dans un journal, utilisé pour désactiver une fonctionnalité sur l'écran du téléphone pendant la projection, ou autre chose.

MainActivity.kt

import androidx.car.app.connection.CarConnection

...

@Composable
fun ProjectionState(carConnectionType: Int, modifier: Modifier = Modifier) {
    val text = when (carConnectionType) {
        CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not projecting"
        CarConnection.CONNECTION_TYPE_NATIVE -> "Running on Android Automotive OS"
        CarConnection.CONNECTION_TYPE_PROJECTION -> "Projecting"
        else -> "Unknown connection type"
    }

    Text(
        text = text,
        style = MaterialTheme.typography.bodyMedium,
        modifier = modifier
    )
}
  1. Maintenant qu'il existe un moyen d'afficher les données, lisez-les et transmettez-les au composable, comme le montre l'extrait de code suivant.

MainActivity.kt

import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState

...

setContent {
    val carConnectionType by CarConnection(this).type.observeAsState(initial = -1)
    PlacesTheme {
        // A surface container using the 'background' color from the theme
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colorScheme.background
        ) {
            Column {
                Text(
                    text = "Places",
                    style = MaterialTheme.typography.displayLarge,
                    modifier = Modifier.padding(8.dp)
                )
                ProjectionState(
                    carConnectionType = carConnectionType,
                    modifier = Modifier.padding(8.dp)
                )
                PlaceList(places = PlacesRepository().getPlaces())
            }
        }
    }
}
  1. Si vous exécutez l'application, le texte Not projecting (Aucune projection) devrait s'afficher.

Une ligne de texte supplémentaire apparaît sur l&#39;écran pour l&#39;état de la projection indiquant &quot;Not projecting&quot; (Aucune projection).

7. Tester avec l'unité principale pour ordinateur (DHU)

Une fois CarAppService implémenté et Android Auto configuré, il est temps d'exécuter l'application et de voir le résultat.

  1. Installez l'application sur votre téléphone et suivez les instructions pour installer et exécuter la DHU.

La DHU étant opérationnelle, vous pouvez voir l'icône de l'application dans le lanceur d'applications (si tel n'est pas le cas, vérifiez que vous avez suivi toutes les étapes de la section précédente, puis quittez et relancez la DHU depuis le terminal).

  1. Ouvrez l'application depuis le lanceur d'applications

Le lanceur d&#39;Android Auto affichant la grille d&#39;applications avec l&#39;application Places

Oups, ça a planté !

Un écran d&#39;erreur affichant le message &quot;Android Auto has encountered an unexpected error&quot; (Android Auto a rencontré une erreur inattendue). Il y a un bouton d&#39;activation/désactivation du débogage en haut à droite de l&#39;écran.

  1. Pour savoir pourquoi l'application a planté, vous pouvez consulter le Logcat dans Android Studio (vous devrez peut-être supprimer le filtre Logcat par défaut pour package:mine et le remplacer par is:error).
Error: [type: null, cause: null, debug msg: java.lang.IllegalArgumentException: Min API level not declared in manifest (androidx.car.app.minCarApiLevel)
        at androidx.car.app.AppInfo.retrieveMinCarAppApiLevel(AppInfo.java:143)
        at androidx.car.app.AppInfo.create(AppInfo.java:91)
        at androidx.car.app.CarAppService.getAppInfo(CarAppService.java:380)
        at androidx.car.app.CarAppBinder.getAppInfo(CarAppBinder.java:255)
        at androidx.car.app.ICarApp$Stub.onTransact(ICarApp.java:182)
        at android.os.Binder.execTransactInternal(Binder.java:1285)
        at android.os.Binder.execTransact(Binder.java:1244)
]

Le journal montre qu'il manque une déclaration dans le fichier manifeste pour le niveau minimum d'API pris en charge par l'application. Avant d'ajouter cette entrée, il est préférable de comprendre pourquoi elle est nécessaire.

Comme Android, la bibliothèque Car App possède également un concept de niveaux d'API, puisqu'il doit exister un contrat entre les applications hôtes et clientes pour qu'elles puissent communiquer. Les applications hôtes prennent en charge un niveau d'API donné et les fonctionnalités qui y sont associées (et, pour des raisons de rétrocompatibilité, celles des niveaux antérieurs également). Par exemple, le SignInTemplate peut être utilisé sur les applications hôtes exécutant un niveau d'API 2 ou supérieur. Mais si vous essayez de l'utiliser sur une application hôte qui ne prend en charge que le niveau d'API 1, cette appli ne connaîtra pas le type de modèle et ne pourra rien faire de significatif avec lui.

Au cours du processus de liaison entre les applications hôte et cliente, il doit y avoir un certain chevauchement des niveaux d'API pris en charge pour que la liaison réussisse. Par exemple, si une application hôte ne prend en charge que le niveau d'API 1, mais qu'une application cliente ne peut pas fonctionner sans les fonctionnalités du niveau d'API 2 (comme indiqué par cette déclaration du fichier manifeste), les applications ne devraient pas se connecter, car l'application cliente ne pourrait pas s'exécuter correctement sur l'application hôte. Ainsi, le niveau minimum d'API requis doit être déclaré par l'application cliente dans son fichier manifeste afin de s'assurer qu'elle sera liée uniquement à une application hôte capable de le prendre en charge.

  1. Pour configurer le niveau minimum d'API requis, ajoutez l'élément <meta-data> dans le fichier AndroidManfiest.xml du module :common:car-app-service :

AndroidManifest.xml (:common:car-app-service)

<application>
    <meta-data
        android:name="androidx.car.app.minCarApiLevel"
        android:value="1" />
    <service android:name="com.example.places.carappservice.PlacesCarAppService" ...>
        ...
    </service>
</application>
  1. Réinstallez l'application et exécutez-la sur la DHU, l'écran suivant devrait s'afficher :

L&#39;application affiche un écran basique avec écrit &quot;Hello, world!&quot;

Par souci d'exhaustivité, vous pouvez également essayer de configurer le minCarApiLevel sur une valeur élevée (par exemple, 100) pour voir ce qui se passe lorsque vous essayez de lancer l'application si les applications hôte et cliente ne sont pas compatibles (indice : l'application plante, comme lorsqu'aucune valeur n'est renseignée).

Notez également que, tout comme Android, vous pouvez utiliser des fonctionnalités d'une API d'un niveau supérieur au niveau minimum déclaré si vous vérifiez lors de l'exécution que l'application hôte prend en charge ce niveau d'API.

Facultatif : écouter les modifications de projection

  • Si vous avez ajouté l'écouteur CarConnection à l'étape précédente, l'état de la projection devrait s'être mis à jour sur votre téléphone lors de l'exécution de la DHU, comme indiqué ci-dessous :

La ligne de texte affichant l&#39;état de la projection indique désormais &quot;Projecting&quot; (Projection) puisque le téléphone est connecté à la DHU.

8. Ajouter la prise en charge d'Android Automotive OS

La version pour Android Auto étant opérationnelle, il est maintenant temps d'ajouter la prise en charge d'Android Automotive OS.

Créer le module :automotive

  1. Pour créer un module contenant le code spécifique à la version Android Automotive OS de l'application, ouvrez File > New > New Module… (Fichier > Nouveau > Nouveau module…) dans Android Studio. Dans la liste des types de modèles, sélectionnez Automotive (Automobile), puis utilisez les valeurs suivantes :
  • Application/Library name (nom de l'application/de la bibliothèque) : Places (le même que celui de l'application principale, mais vous pourriez le modifier si vous le souhaitiez)
  • Module name (nom du module) : automotive
  • Package name (nom du package) : com.example.places.automotive
  • Langue : Kotlin
  • SDK minimum : API 29: Android 10.0 (Q). Tous les véhicules équipés d'Android Automotive OS prenant en charge les applications de la bibliothèque Car App exécutent un niveau d'API 29 au minimum.

L&#39;assistant &quot;Create New Module&quot; (Créer un nouveau module) pour le module Android Automotive OS affichant les valeurs listées ci-dessus.

  1. Cliquez sur Next (Suivant), puis sélectionnez No Activity (Aucune activité) sur l'écran suivant et cliquez sur Finish (Terminer).

La seconde page de l&#39;assistant &quot;Create New Module&quot; (Créer un nouveau module). Trois options s&#39;affichent : &quot;No Activity&quot; (Aucune activité), &quot;Media Service&quot; (Service multimédia), et &quot;Messaging Service&quot; (Service de messagerie). L&#39;option &quot;No Activity&quot; (Aucune activité) est sélectionnée.

Ajouter des dépendances

Tout comme pour Android Auto, vous devez déclarer une dépendance au module :common:car-app-service. Vous pourrez ainsi partager votre implémentation sur les deux plates-formes.

Vous devez également ajouter une dépendance à l'artefact androidx.car.app:app-automotive. Contrairement à l'artefact androidx.car.app:app-projected, facultatif pour Android Auto, cette dépendance est obligatoire pour Android Automotive OS, car elle inclut la CarAppActivity utilisées pour exécuter l'application.

  1. Commencez par ajouter une entrée pour l'artefact androidx.car.app:app-automotive dans libs.versions.toml.

libs.version.toml

[libraries]
...
androidx-car-app-automotive = { group = "androidx.car.app", name = "app-automotive", version.ref = "carApp"}
  1. Pour ajouter des dépendances, ouvrez le fichier build.gradle.kts et insérez le code suivant :

build.gradle.kts (Module :automotive)

dependencies {
    ...
    implementation(project(":common:car-app-service"))
    implementation(libs.androidx.car.app.automotive)
    ...
}

Avec ce changement, le graphique de dépendance pour les modules propres à l'application est le suivant :

Les modules :app et :common:car-app-service dépendent tous les deux du module :common:data. Les modules :app et :automotive dépendant du module :common:car-app-service.

Configurer le fichier manifeste

  1. Commencez par déclarer deux fonctionnalités obligatoires, android.hardware.type.automotive et android.software.car.templates_host.

android.hardware.type.automotive est une fonctionnalité système qui indique que l'appareil est un véhicule (pour plus d'informations, consultez la section FEATURE_AUTOMOTIVE). Seules les applications qui marquent cette fonctionnalité comme obligatoire peuvent être soumises à un canal Automotive OS sur la Play Console (les applications soumises à d'autres canaux ne peuvent pas exiger cette fonctionnalité). android.software.car.templates_host est une fonctionnalité système présente uniquement dans les véhicules pour lesquels l'hôte du modèle est exigé pour exécuter les applications modèles. Pour cet atelier de programmation, ces modifications suffisent. Lorsque vous créez votre propre application, assurez-vous qu'elle répond à toutes les exigences de Google Play pour Android Automotive OS.

AndroidManifest.xml (:automotive)

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-feature
        android:name="android.hardware.type.automotive"
        android:required="true" />
    <uses-feature
        android:name="android.software.car.templates_host"
        android:required="true" />
    ...
</manifest>
  1. Ajoutez ensuite la référence au fichier automotive_app_desc.xml, comme vous l'aviez fait pour Android Auto.

Notez que cette fois l'attribut android:name est différent, il ne s'agit plus de com.google.android.gms.car.application, mais de com.android.automotive. Comme précédemment, cela permet de référencer le fichier automotive_app_desc.xml dans le module :common:car-app-service, ce qui signifie que la même ressource est utilisée pour Android Auto et Android Automotive OS. De plus, l'élément <meta-data> se trouve dans l'élément <application> (vous devez donc modifier la balise application pour qu'elle ne soit plus autofermante).

AndroidManifest.xml (:automotive)

<application>
    ...
    <meta-data android:name="com.android.automotive"
        android:resource="@xml/automotive_app_desc"/>
    ...
</application>
  1. Enfin, vous devez ajouter un élément <activity> pour la CarAppActivity de la bibliothèque.

AndroidManifest.xml (:automotive)

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
    <application ...>
        ...
        <activity
            android:name="androidx.car.app.activity.CarAppActivity"
            android:exported="true"
            android:launchMode="singleTask"
            android:theme="@android:style/Theme.DeviceDefault.NoActionBar">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="distractionOptimized"
                android:value="true" />
        </activity>
    </application>
</manifest>

Et voici à quoi servent toutes ces configurations :

  • android:name liste le nom de classe complet de la classe CarAppActivity depuis le package app-automotive.
  • android:exported est défini sur true puisque cette Activity doit être exécutable par une autre application (le lanceur d'applications).
  • android:launchMode est défini sur singleTask afin qu'il ne puisse y avoir qu'une instance de CarAppActivity à la fois.
  • android:theme est défini sur @android:style/Theme.DeviceDefault.NoActionBar afin que l'application occupe tout l'espace sur l'écran.
  • Le fichier d'intent indique qu'il s'agit du lanceur d'applications Activity pour l'application.
  • Un élément <meta-data> indique au système que l'application peut être utilisée lorsque les restrictions de l'expérience utilisateur sont en place, lorsque le véhicule roule par exemple.

Facultatif : copier les icônes de lanceur depuis le module :app

Puisque vous venez de créer le module :automotive, il affiche les icônes vertes par défaut du logo Android.

  • Si vous le souhaitez, vous pouvez copier et coller le répertoire de ressources mipmap depuis le module :app dans le module :automotive pour utiliser les mêmes icônes de lanceur que l'application mobile.

9. Effectuer des tests avec l'émulateur d'Android Automotive OS

Créer un appareil virtuel Android pour Android Automotive OS

  1. Après avoir ouvert le gestionnaire d'appareils, sélectionnezAutomotive (Automobile) dans la colonne Category (Catégorie) à gauche de la fenêtre. Sélectionnez ensuite la définition de l'appareil Automobile (1408p landscape) with Google Play (Automobile [paysage 1408 p] avec Google Play) dans la liste, puis cliquez sur Next (Suivant).

L&#39;assistant &quot;Virtual Device Configuration&quot; (Configuration des appareils virtuels) affiche le profil matériel &quot;Automotive (1024p landscape)&quot; (Automobile (paysage 1024p)) sélectionné.

  1. Sur la page suivante, sélectionnez l'image système de l'API 34 (elle sera téléchargée si ce n'est pas déjà fait), puis créez l'AVD en cliquant sur Finish (Terminer).

c9631de84fc6e835.png

Exécuter l'application

Exécutez l'application sur l'émulateur que vous venez de créer en utilisant la configuration d'exécution automotive.

La page

L&#39;application affiche un écran basique avec écrit &quot;Hello, world!&quot;

Dans la prochaine étape, vous apporterez des changements au module :common:car-app-service afin d'afficher une liste de lieux et de permettre à l'utilisateur de lancer la navigation vers une destination donnée depuis une autre application.

10. Ajouter une carte et un écran d'informations

Ajouter une carte à l'écran principal

  1. Pour commencer, remplacez le code dans la méthode onGetTemplate de la classe MainScreen par ce qui suit :

MainScreen.kt

import androidx.car.app.model.CarLocation
import androidx.car.app.model.Distance
import androidx.car.app.model.DistanceSpan
import androidx.car.app.model.ItemList
import androidx.car.app.model.Metadata
import androidx.car.app.model.Place
import androidx.car.app.model.PlaceListMapTemplate
import androidx.car.app.model.PlaceMarker
import com.example.places.data.PlacesRepository

...

override fun onGetTemplate(): Template {
    val placesRepository = PlacesRepository()
    val itemListBuilder = ItemList.Builder()
        .setNoItemsMessage("No places to show")

    placesRepository.getPlaces()
        .forEach {
            itemListBuilder.addItem(
                Row.Builder()
                    .setTitle(it.name)
                    // Each item in the list *must* have a DistanceSpan applied to either the title
                    // or one of the its lines of text (to help drivers make decisions)
                    .addText(SpannableString(" ").apply {
                        setSpan(
                            DistanceSpan.create(
                                Distance.create(Math.random() * 100, Distance.UNIT_KILOMETERS)
                            ), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE
                        )
                    })
                    .setOnClickListener { TODO() }
                    // Setting Metadata is optional, but is required to automatically show the
                    // item's location on the provided map
                    .setMetadata(
                        Metadata.Builder()
                            .setPlace(Place.Builder(CarLocation.create(it.latitude, it.longitude))
                                // Using the default PlaceMarker indicates that the host should
                                // decide how to style the pins it shows on the map/in the list
                                .setMarker(PlaceMarker.Builder().build())
                                .build())
                            .build()
                    ).build()
            )
        }

    return PlaceListMapTemplate.Builder()
        .setTitle("Places")
        .setItemList(itemListBuilder.build())
        .build()
}

Ce code lit la liste des instances Place depuis le PlacesRepository et les convertit en une Row à ajouter à la ItemList affichée dans le PlaceListMapTemplate.

  1. Exécutez à nouveau l'application (sur l'une des plates-formes ou les deux) pour voir le résultat :

Android Auto

Android Automotive OS

L&#39;application vient de planter et l&#39;utilisateur est renvoyé au lanceur d&#39;applications après avoir ouvert l&#39;application.

Oups, une nouvelle erreur. Une autorisation semble manquante.

java.lang.SecurityException: The car app does not have a required permission: androidx.car.app.MAP_TEMPLATES
        at android.os.Parcel.createExceptionOrNull(Parcel.java:2373)
        at android.os.Parcel.createException(Parcel.java:2357)
        at android.os.Parcel.readException(Parcel.java:2340)
        at android.os.Parcel.readException(Parcel.java:2282)
        ...
  1. Pour corriger l'erreur, ajoutez l'élément <uses-permission> suivant dans le fichier manifeste du module :common:car-app-service.

L'autorisation doit être déclarée par n'importe quelle application qui utilise le PlaceListMapTemplate. Autrement l'application plante, comme nous venons de le voir. Notez que seules les applications qui déclarent leur catégorie comme androidx.car.app.category.POI peuvent utiliser ce modèle et, par extension, cette autorisation.

AndroidManifest.xml (:common:car-app-service)

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="androidx.car.app.MAP_TEMPLATES" />
    ...
</manifest>

Si vous exécutez l'application après avoir ajouté l'autorisation, vous devriez obtenir le résultat suivant sur chaque plate-forme :

Android Auto

Android Automotive OS

Une liste des lieux est affichée sur le côté gauche de l&#39;écran et une carte avec des épingles correspondant à ces mêmes lieux s&#39;affiche derrière, en remplissant le reste de l&#39;écran.

Une liste des lieux est affichée sur le côté gauche de l&#39;écran et une carte avec des épingles correspondant à ces mêmes lieux s&#39;affiche derrière, en remplissant le reste de l&#39;écran.

L'affichage de la carte est pris en charge par l'hôte de l'application lorsque vous fournissez les Metadata nécessaires.

Ajouter un écran d'informations

Maintenant, il est temps d'ajouter un écran d'informations pour permettre aux utilisateurs d'en savoir plus sur un lieu spécifique et d'avoir la possibilité de s'y rendre en utilisant leur application de navigation préférée ou de retourner à la liste des lieux. Pour ce faire, vous pouvez utiliser PaneTemplate, qui vous permet d'afficher jusqu'à quatre lignes d'informations à côté des boutons d'action facultatifs.

  1. Tout d'abord, faites un clic droit sur le répertoire res dans le module :common:car-app-service, puis sur New > Vector Asset (Nouveau > Élément vectoriel), et utilisez la configuration suivante pour créer une icône de navigation :
  • Asset type (type d'élément) : Clip art
  • Clip art (Image clipart) : navigation
  • Nom : baseline_navigation_24
  • Size (Taille) : 24 dp by 24 dp (24 dp par 24 dp)
  • Couleur : #000000
  • Opacity (Opacité) : 100%

Assistant Asset Studio affichant les valeurs mentionnées ci-dessus.

  1. Ensuite, créez un fichier intitulé DetailScreen.kt dans le package screen (à côté du fichier MainScreen.kt existant) et ajoutez le code suivant :

DetailScreen.kt

import android.graphics.Color
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon
import androidx.car.app.model.Header
import androidx.car.app.model.MessageTemplate
import androidx.car.app.model.Pane
import androidx.car.app.model.PaneTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.Template
import androidx.core.graphics.drawable.IconCompat
import com.example.android.cars.carappservice.R
import com.example.places.data.PlacesRepository
import com.example.places.data.model.toIntent

class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) {
    private var isBookmarked = false

    override fun onGetTemplate(): Template {
        val place = PlacesRepository().getPlace(placeId)
            ?: return MessageTemplate.Builder("Place not found")
                .setHeader(
                    Header.Builder()
                        .setStartHeaderAction(Action.BACK)
                        .build()
                )
                .build()

        val navigateAction = Action.Builder()
            .setTitle("Navigate")
            .setIcon(
                CarIcon.Builder(
                    IconCompat.createWithResource(
                        carContext,
                        R.drawable.baseline_navigation_24
                    )
                ).build()
            )
            // Only certain Intent actions are supported by `startCarApp`. Check its documentation
            // for all of the details. To open another app that can handle navigating to a location
            // you must use the CarContext.ACTION_NAVIGATE action and not Intent.ACTION_VIEW like
            // you might on a phone.
            .setOnClickListener { carContext.startCarApp(place.toIntent(CarContext.ACTION_NAVIGATE)) }
            .build()

        return PaneTemplate.Builder(
            Pane.Builder()
                .addAction(navigateAction)
                .addRow(
                    Row.Builder()
                        .setTitle("Coordinates")
                        .addText("${place.latitude}, ${place.longitude}")
                        .build()
                ).addRow(
                    Row.Builder()
                        .setTitle("Description")
                        .addText(place.description)
                        .build()
                ).build()
        ).setHeader(
            Header.Builder()
                .setStartHeaderAction(Action.BACK)
                .setTitle(place.name)
                .build()
        ).build()
    }
}

Faites particulièrement attention à la construction de navigateAction. L'appel à startCarApp dans son OnClickListener est essentiel pour interagir avec d'autres applications sur Android Auto et Android Automotive OS.

Maintenant qu'il y a deux types d'écrans, il est temps d'ajouter la navigation entre les deux. La navigation dans la bibliothèque Car App Library utilise un modèle de pile avec empilement et dépilement, idéal pour les flux de tâches simples pendant la conduite.

Un schéma qui représente le fonctionnement de la navigation dans l&#39;application avec la bibliothèque Car App. À gauche se trouve une pile &quot;MainScreen&quot; (Écran principal). Une flèche intitulée &quot;Push DetailScreen&quot; (Empiler l&#39;écran principal) la relie à la pile centrale. La pile centrale comporte un &quot;DetailScreen&quot; (Écran détaillé) situé au-dessus de la pile &quot;MainScreen&quot;. Une flèche intitulée &quot;Pop&quot; (Dépiler) relie la pile centrale à la pile de droite. Les piles de droite et de gauche sont toutes deux intitulées &quot;MainScreen&quot;.

  1. Pour que l'un des éléments de la liste passe vers MainScreen ou DetailScreen, ajoutez le code suivant :

MainScreen.kt

Row.Builder()
    ...
    .setOnClickListener { screenManager.push(DetailScreen(carContext, it.id)) }
    ...

Le retour depuis un DetailScreen vers le MainScreen est déjà configuré puisque setHeaderAction(Action.BACK) est appelé lors de la création du PaneTemplate affiché sur le DetailScreen. Lorsque l'utilisateur clique sur l'action d'en-tête, l'hôte se charge de dépiler l'écran actuel de la pile, mais ce comportement peut être ignoré par votre application, si vous le souhaitez.

  1. Exécutez votre application pour voir le fonctionnement du DetailScreen et de la navigation dans votre application.

11. Mettre à jour le contenu sur un écran

Vous souhaitez sans doute permettre à un utilisateur d'interagir avec un écran et de modifier l'état des éléments qui s'y trouvent. Pour ce faire, créez une fonctionnalité permettant aux utilisateurs d'ajouter ou de supprimer un favori pour un lieu à partir de DetailScreen.

  1. Dans un premier temps, ajoutez une variable locale, isBookmarked qui retient l'état. Dans une application réelle, cette variable devrait être stockée dans la couche de données, mais une variable locale suffit à des fins de démonstration.

DetailScreen.kt

class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) {
    private var isBookmarked = false
    ...
}
  1. Ensuite, effectuez un clic droit sur le répertoire res dans le module :common:car-app-service, puis sur New > Vector Asset (Nouveau > Élément vectoriel), et utilisez la configuration suivante pour créer deux icônes de favoris :
  • Asset type (type d'élément) : Clip art
  • Nom : outline_bookmark_add_24, outline_bookmark_added_24
  • Images clipart : bookmark, bookmark_added (de l'ensemble de sources "Material Symbols Outlined" [Symboles Material Design en traits pleins])
  • Size (Taille) : 24 dp by 24 dp (24 dp par 24 dp)
  • Couleur : #000000
  • Opacity (Opacité) : 100%
  1. Ensuite, dans DetailsScreen.kt, créez un Action pour la fonctionnalité d'ajout aux favoris.

DetailScreen.kt

val navigateAction = ...

val bookmarkAction = Action.Builder()
    .setIcon(
        CarIcon.Builder(
            IconCompat.createWithResource(
                carContext,
                if (isBookmarked) R.drawable.outline_bookmark_added_24 else R.drawable.outline_bookmark_add_24
            )
        ).build()
    )
    .setOnClickListener {
        isBookmarked = !isBookmarked
    }.build()

...

Deux éléments sont intéressants ici :

  • La couleur de CarIcon change en fonction de l'état de l'élément.
  • setOnClickListener est utilisé pour réagir aux entrées de l'utilisateur et modifier l'état de favori.
  1. N'oubliez pas d'appeler addEndHeaderAction sur PaneTemplate.Builder pour utiliser bookmarkAction.

DetailScreen.kt

Header.Builder()
    ...
    .addEndHeaderAction(bookmarkAction)
    .build()
  1. Exécutez maintenant l'application et observez :

Le &quot;DetailScreen&quot; s&#39;affiche. L&#39;utilisateur appuie sur l&#39;icône de favoris, mais celle-ci ne change pas de couleur comme elle le devrait.

Les clics ont bien été reçus, mais l'icône ne change pas.

Ceci est dû au fait que la bibliothèque Car App Library possède un concept d'actualisation. Pour limiter la distraction du conducteur, l'actualisation du contenu sur l'écran est limitée (cette limitation varie en fonction du modèle affiché) et chaque actualisation doit être explicitement demandée par votre propre code en appelant la méthode invalidate de la classe Screen. La simple mise à jour de certains états référencés dans onGetTemplate ne suffit pas pour mettre à jour l'interface utilisateur.

  1. Pour corriger ce problème, mettez à jour le OnClickListener pour le bookmarkAction comme suit :

DetailScreen.kt

.setOnClickListener {
    isBookmarked = !isBookmarked
    // Request that `onGetTemplate` be called again so that updates to the
    // screen's state can be picked up
    invalidate()
}
  1. Exécutez à nouveau l'application pour voir que l'icône est maintenant mise à jour lorsque vous cliquez dessus.

96da7958632706bb.gif

Vous disposez maintenant d'une application de base, bien intégrée à Android Auto et à Android Automotive OS !

12. Félicitations

Vous avez réussi à créer votre première application avec la bibliothèque Car App. Il est maintenant temps d'appliquer ce que vous avez appris à votre propre application !

Comme indiqué précédemment, seules certaines catégories créées à l'aide de la bibliothèque Car App peuvent être proposées au Play Store à l'heure actuelle. Si votre application appartient à l'une des catégories compatibles, vous pouvez commencer à la créer dès aujourd'hui !

De nouvelles catégories d'applications sont ajoutées chaque année, alors même si vous ne pouvez pas appliquer immédiatement ce que vous avez appris, revenez plus tard et vous pourrez peut-être étendre votre application aux véhicules !

À essayer

  • Installez un émulateur d'OEM et observez comment la personnalisation de l'OEM peut modifier l'apparence des applications de la bibliothèque Car App sur Android Automotive OS. Notez que tous les émulateurs OEM ne sont pas compatibles avec les applications de la bibliothèque Car App.
  • Utilisez différentes configurations DHU et différents profils matériels de l'émulateur Android Automotive OS pour voir comment les applications hôtes gèrent l'adaptation de l'application à différentes tailles d'affichage.
  • Consultez l'exemple d'application Showcase qui présente toutes les fonctionnalités de la bibliothèque Car App.

Complément d'informations

Documents de référence