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
- La dernière version d'Android Studio
- Expérience avec le langage Kotlin de base.
- Connaissances de base sur les Services Android.
- Expérience avec la création d'appareils virtuels Android et leur exécution dans Android Emulator.
- Connaissances de base en modularisation des applications Android.
- Connaissances de base sur le schéma de conception Builder.
Objectifs de l'atelier
Android Auto | 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
etScreen
. - 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
- Le code pour cet atelier de programmation se trouve dans le répertoire
car-app-library-fundamentals
au sein du dépôt GitHubcar-codelabs
. Pour le cloner, exécutez la commande suivante :
git clone https://github.com/android/car-codelabs.git
- Vous pouvez aussi télécharger le dépôt sous la forme d'un 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épertoirecar-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
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.
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
- Sélectionnez le module
:common
dans la fenêtre Project (Projet), faites un clic droit et sélectionnez l'option New > Module (Nouveau > Module). - 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)
Configurer des dépendances
- Dans le fichier
libs.version.toml
, ajoutez des entrées pour l'artefactandroidx.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" }
- 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, etandroidx.car.app:app-testing
pour certains assistants utiles lors des tests unitaires. Vous utiliserezapp-projected
etapp-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 :
Maintenant que vous avez configuré les dépendances, passons à l'écriture de CarAppService
.
5. Écrire votre CarAppService
- Commencez par créer un fichier intitulé
PlacesCarAppService.kt
dans le packagecarappservice
à l'intérieur du module:common:car-app-service
. - Dans ce fichier, créez une classe intitulée
PlacesCarAppService
, qui étendCarAppService
.
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.
- Enfin, vous devez ajouter l'élément
<service>
qui correspond auPlacesCarAppService
dans le fichierAndroidManifest.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.
- Effectuez un clic droit sur le package
com.example.places.carappservice
et sélectionnez New > Package (Nouveau > Package), le nom complet du package sera alorscom.example.places.carappservice.screen
. C'est à cet endroit que vous mettez toutes les sous-classesScreen
de l'application. - Dans le package
screen
, créez un fichier intituléMainScreen.kt
pour contenir la classeMainScreen
, qui étendScreen
. Pour le moment, un simple message Hello, world! grâce auPaneTemplate
.
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 :
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
- 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
- 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>
- Dans le fichier
AndroidManifest.xml
du module:app
, ajoutez l'élément suivant<meta-data>
qui référence le fichierautomotive_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.
- Pour utiliser l'API
CarConnection
, commencez par ajouter une dépendance au module:app
dans l'artefactandroidx.car.app:app
.
build.gradle.kts (Module :app)
dependencies {
...
implementation(libs.androidx.car.app)
...
}
- À 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
)
}
- 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())
}
}
}
}
- Si vous exécutez l'application, le texte Not projecting (Aucune projection) devrait s'afficher.
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.
- 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).
Oups, ça a planté !
- 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 paris: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.
- Pour configurer le niveau minimum d'API requis, ajoutez l'élément
<meta-data>
dans le fichierAndroidManfiest.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>
- Réinstallez l'application et exécutez-la sur la DHU, l'écran suivant devrait s'afficher :
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 :
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
- 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.
- Cliquez sur Next (Suivant), puis sélectionnez No Activity (Aucune activité) sur l'écran suivant et cliquez sur Finish (Terminer).
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.
- Commencez par ajouter une entrée pour l'artefact
androidx.car.app:app-automotive
danslibs.versions.toml
.
libs.version.toml
[libraries]
...
androidx-car-app-automotive = { group = "androidx.car.app", name = "app-automotive", version.ref = "carApp"}
- 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 :
Configurer le fichier manifeste
- Commencez par déclarer deux fonctionnalités obligatoires,
android.hardware.type.automotive
etandroid.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>
- 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>
- Enfin, vous devez ajouter un élément
<activity>
pour laCarAppActivity
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 classeCarAppActivity
depuis le packageapp-automotive
.android:exported
est défini surtrue
puisque cetteActivity
doit être exécutable par une autre application (le lanceur d'applications).android:launchMode
est défini sursingleTask
afin qu'il ne puisse y avoir qu'une instance deCarAppActivity
à 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
- 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).
- 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).
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
.
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
- Pour commencer, remplacez le code dans la méthode
onGetTemplate
de la classeMainScreen
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
.
- Exécutez à nouveau l'application (sur l'une des plates-formes ou les deux) pour voir le résultat :
Android Auto | Android Automotive OS |
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) ...
- 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 |
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.
- 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 by24
dp (24
dp par24
dp) - Couleur :
#000000
- Opacity (Opacité) :
100%
- Ensuite, créez un fichier intitulé
DetailScreen.kt
dans le packagescreen
(à côté du fichierMainScreen.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.
Naviguer entre les écrans
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.
- Pour que l'un des éléments de la liste passe vers
MainScreen
ouDetailScreen
, 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.
- 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
.
- 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
...
}
- 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 by24
dp (24
dp par24
dp) - Couleur :
#000000
- Opacity (Opacité) :
100%
- Ensuite, dans
DetailsScreen.kt
, créez unAction
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.
- N'oubliez pas d'appeler
addEndHeaderAction
surPaneTemplate.Builder
pour utiliserbookmarkAction
.
DetailScreen.kt
Header.Builder()
...
.addEndHeaderAction(bookmarkAction)
.build()
- Exécutez maintenant l'application et observez :
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.
- Pour corriger ce problème, mettez à jour le
OnClickListener
pour lebookmarkAction
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()
}
- Exécutez à nouveau l'application pour voir que l'icône est maintenant mise à jour lorsque vous cliquez dessus.
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
- L'atelier de programmation Utiliser la bibliothèque d'applications Android for Cars couvre le contenu de cet atelier de programmation et bien plus encore.
- Les Consignes de conception de la bibliothèque d'applications Android for Cars fournit une description détaillée des différents modèles et des bonnes pratiques à suivre lors de la création de votre application.
- La page Qualité des applis Android pour les voitures décrit les critères auxquels votre application doit répondre pour créer une expérience utilisateur optimale et réussir l'examen du Play Store.