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 !

Ce que cet atelier n'est pas

Ce dont vous avez besoin

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 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.

Screen

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 créerez CarAppService, Session et Screen dans la section Créer votre CarAppService de cet atelier. Ne vous inquiétez pas si tout ne fonctionne pas correctement pour le moment.

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 build.gradle au niveau du projet, ajoutez une déclaration de variable pour la version de la bibliothèque Car App, comme indiqué ci-dessous. Vous pourrez ainsi facilement utiliser la même version sur chacun des modules dans l'application.

build.gradle (Project: Places)

buildscript {
    ext {
        // All versions can be found at https://developer.android.com/jetpack/androidx/releases/car-app
        car_app_library_version = '1.3.0-rc01'
        ...
    }
}
  1. Ensuite, ajoutez deux dépendances au fichier build.gradle 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 (Module :common:car-app-service)

dependencies {
    ...
    implementation "androidx.car.app:app:$car_app_library_version"
    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

class PlacesCarAppService : CarAppService() {

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

    override fun onCreateSession(): 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, qui détermine les critères de qualité auxquels l'application doit répondre (nous y reviendrons plus tard). androidx.car.app.category.NAVIGATION et androidx.car.app.category.IOT sont d'autres valeurs possibles.

Créer la classe PlacesSession

  • Créez un fichier PlacesCarAppService.kt et ajoutez le code suivant :

PlacesCarAppService.kt

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

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)
            .setHeaderAction(Action.APP_ICON)
            .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 du module :app, ajoutez ceci :

build.gradle (Module :app)

dependencies {
    ...
    implementation project(path: ':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 (Module :app)

dependencies {
    ...
    implementation "androidx.car.app:app:$car_app_library_version"
    ...
}
  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

@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

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 soit activer le bouton de débogage en bas à droite (visible uniquement lorsque l'application est exécutée sur la DHU), soit vérifier le Logcat dans Android Studio.

Le même écran d&#39;erreur que précédemment, mais avec l&#39;option de débogage activée. Une trace de la pile est affichée sur l&#39;écran.

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
  • Language (langue) : Kotlin
  • SDK minimum : API 29: Android 10.0 (Q). Comme mentionné précédemment lors de la création du module :common:car-app-service, 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. Pour ajouter des dépendances, ouvrez le fichier build.gradle et insérez le code suivant :

build.gradle (Module :automotive)

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

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.

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. Vous devez ensuite déclarer certaines fonctionnalités comme n'étant pas obligatoires.

Cela permet de s'assurer que votre application est compatible avec la gamme de matériel disponible dans les voitures avec Google intégré. Par exemple, si votre application nécessite la fonctionnalité android.hardware.screen.portrait, elle n'est pas compatible avec les véhicules équipés d'écrans en orientation paysage, l'orientation étant fixe dans la plupart des véhicules. C'est pourquoi l'attribut android:required est défini sur false pour ces fonctionnalités.

AndroidManifest.xml (:automotive)

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
    <uses-feature
        android:name="android.hardware.wifi"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.screen.portrait"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.screen.landscape"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.camera"
        android:required="false" />
    ...
</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. Notez que 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

Installer les images système Automotive avec Play Store

  1. Pour commencer, ouvrez SDK Manager dans Android Studio et sélectionnez l'onglet SDK Platforms (Plates-formes de SDK) si ce n'est pas déjà fait. En bas à droite de la fenêtre SDK Manager, assurez-vous que la case Show package details (Afficher les détails du package) est cochée.
  2. Installez une ou plusieurs des images d'émulateur suivantes. Les images ne s'exécutent que sur les machines disposant de la même architecture (x86/ARM).
  • Android 12L > Image système Automotive avec Play Store Intel x86 Atom_64
  • Android 12L > Image système Automotive avec Play Store ARM 64 v8a
  • Android 11 > Image système Automotive avec Play Store Intel x86 Atom_64
  • Android 10 > Automotive avec Play Store Intel x86 Atom_64

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. Puis, sélectionnez la définition de l'appareil Automotive (1024p landscape) (Automobile [paysage 1024 p]) dans la liste et 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 une image système de l'étape précédente (si vous choisissez l'image Android 11/API 30, il se peut qu'elle se trouve dans l'onglet x86 Images (Images x86) et non dans l'onglet Recommended (Recommandé) par défaut). Cliquez sur Next (Suivant), sélectionnez les options avancées de votre choix, puis créez l'AVD (appareil Android virtuel) en cliquant sur Finish (Terminer).

Exécuter l'application

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

La page

Lorsque vous exécutez l'application pour la première fois, un écran semblable à celui-ci peut s'afficher :

L&#39;application affiche un écran indiquant &quot;System update required&#39; (Mise à jour système requise) avec un bouton &quot;Check for updates&quot; (Rechercher les mises à jour) juste en dessous.

Si tel est le cas, cliquez sur le bouton Check for updates (Rechercher les mises à jour), qui vous redirige vers la page de l'application Google Automotive App Host du Play Store, puis cliquez sur le bouton Installer. Si vous n'êtes pas connecté lorsque vous cliquez sur le bouton Check for updates (Rechercher les mises à jour), vous serez redirigé vers la page de connexion. Une fois connecté, vous pouvez rouvrir l'application, cliquer sur le bouton et retourner sur le page du Play Store.

La page de l&#39;application Google Automotive App Host du Play Store sur laquelle se trouve un bouton &quot;Install&quot; (Installer) dans le coin en haut à droite.

  1. Enfin, une fois l'hôte installé, ouvrez à nouveau l'application à partir du lanceur d'applications (l'icône de grille à neuf points dans la ligne en bas de l'écran) et vous devriez voir ceci :

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

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

Une autre trace de la pile s&#39;affiche à cause d&#39;une erreur

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
  • Name (Nom) : baseline_navigation_24
  • Size (Taille) : 24 dp by 24 dp (24 dp par 24 dp)
  • Color (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

class DetailScreen(carContext: CarContext, private val placeId: Int) : Screen(carContext) {

    override fun onGetTemplate(): Template {
        val place = PlacesRepository().getPlace(placeId)
            ?: return MessageTemplate.Builder("Place not found")
                .setHeaderAction(Action.BACK)
                .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()
        )
            .setTitle(place.name)
            .setHeaderAction(Action.BACK)
            .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 un lieu à leurs favoris, ou de l'en supprimer, à partir du DetailScreen.

  1. Dans un premier temps, ajoutez une variable locale, isFavorite 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 isFavorite = 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 une icône de favoris :
  • Asset type (type d'élément) : Clip art
  • Name (Nom) : baseline_favorite_24
  • Clip art (Image clipart) : favorite
  • Size (Taille) : 24 dp by 24 dp (24 dp par 24 dp)
  • Color (Couleur) : #000000
  • Opacity (Opacité) : 100%

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

  1. Enfin, dans DetailsScreen.kt, créez une ActionStrip pour le PaneTemplate.

Les composants de l'interface utilisateur ActionStrip sont placés dans la ligne d'en-tête, à l'opposé du titre, et sont idéaux pour les actions secondaires et tertiaires. Étant donné que la navigation est la principale action requise sur le DetailScreen, placer l'Action de sorte d'ajouter ou de supprimer des favoris dans une ActionStrip est un excellent moyen de structurer l'écran.

DetailScreen.kt

val navigateAction = ...

val actionStrip = ActionStrip.Builder()
    .addAction(
        Action.Builder()
            .setIcon(
                CarIcon.Builder(
                    IconCompat.createWithResource(
                        carContext,
                        R.drawable.baseline_favorite_24
                    )
                ).setTint(
                    if (isFavorite) CarColor.RED else CarColor.createCustom(
                        Color.LTGRAY,
                        Color.DKGRAY
                    )
                ).build()
            )
            .setOnClickListener {
                isFavorite = !isFavorite
            }.build()
    )
    .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 la setActionStrip sur le PaneTemplate.Builder pour l'utiliser !

DetailScreen.kt

return PaneTemplate.Builder(...)
    ...
    .setActionStrip(actionStrip)
    .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.

Intéressant… Il semblerait que les clics se produisent, mais que l'interface utilisateur ne se met pas à jour.

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 comme suit :

DetailScreen.kt

.setOnClickListener {
    isFavorite = !isFavorite
    // 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. La couleur de l'icône cœur devrait changer à chaque clic !

Le &quot;DetailScreen&quot; s&#39;affiche. L&#39;utilisateur appuie sur l&#39;icône de favoris, qui change désormais de couleur comme prévu.

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 est une application de navigation, une application de point d'intérêt (POI) (comme celle sur laquelle vous avez travaillé dans cet atelier de programmation), ou une application Internet des objets (IoT), vous pouvez commencer à la créer aujourd'hui et la publier pour qu'elle soit en production sur les deux plates-formes.

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 (par exemple, l'émulateur Polestar 2) 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.
  • 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