Utiliser la bibliothèque d'applications Android for Cars

La bibliothèque d'applications Android for Cars vous permet d'utiliser vos applications de navigation, de point d'intérêt (POI) et IoT (Internet des objets) dans votre voiture. Pour ce faire, il fournit un ensemble de modèles conçus pour répondre aux normes de distraction du conducteur et prend en charge des détails tels que les différents facteurs d'écran de voiture et les modalités de saisie.

Ce guide présente les fonctionnalités et concepts clés de la bibliothèque, et vous guide tout au long du processus de configuration d'une application simple. Pour une présentation complète, consultez l'atelier de programmation sur les principes de base de la bibliothèque d'applications pour voitures.

Avant de commencer

  1. Examinez les pages Design for Driving (Conception pour la conduite) concernant la bibliothèque d'applications pour voitures.
  2. Passez en revue les termes et concepts clés dans la section suivante.
  3. Familiarisez-vous avec l'interface utilisateur du système Android Auto et la conception d'Android Automotive OS.
  4. Consultez les notes de version.
  5. Consultez les exemples.

Termes et concepts clés

Modèles et modèles
L'interface utilisateur est représentée par un graphique d'objets de modèle qui peuvent être organisés de différentes manières, selon le modèle auquel ils appartiennent. Les modèles constituent un sous-ensemble de modèles pouvant servir de racine dans ces graphiques. Les modèles incluent les informations à présenter à l'utilisateur sous forme de texte et d'images, ainsi que des attributs permettant de configurer certains aspects de l'apparence visuelle de ces informations (par exemple, la couleur du texte ou la taille des images). L'hôte convertit les modèles en vues conçues pour répondre aux normes concernant la distraction du conducteur et prend en charge des détails tels que la variété des facteurs d'écran de la voiture et les modalités de saisie.
Hôte
L'hôte est le composant backend qui implémente la fonctionnalité offerte par les API de la bibliothèque afin que votre application puisse s'exécuter dans la voiture. Les responsabilités de l'hôte vont de la découverte de votre application à la gestion de son cycle de vie, à la conversion de vos modèles en vues et à la notification à votre application des interactions utilisateur. Sur les appareils mobiles, cet hôte est implémenté par Android Auto. Sur Android Automotive OS, cet hôte est installé en tant qu'application système.
Restrictions des modèles
Les différents modèles appliquent des restrictions dans le contenu de leurs modèles. Par exemple, les modèles de liste limitent le nombre d'éléments pouvant être présentés à l'utilisateur. Les modèles comportent également des restrictions sur la manière dont ils peuvent être connectés pour former le flux d'une tâche. Par exemple, l'application ne peut ajouter que cinq modèles à la pile d'écran. Pour en savoir plus, consultez Restrictions liées aux modèles.
Screen
Screen est une classe fournie par la bibliothèque que les applications implémentent pour gérer l'interface utilisateur présentée à l'utilisateur. Un Screen possède un cycle de vie et fournit le mécanisme permettant à l'application d'envoyer le modèle à afficher lorsque l'écran est visible. Les instances Screen peuvent également être transférées vers et depuis une pile Screen, et être transférées vers celle-ci, ce qui garantit qu'elles respectent les restrictions de flux des modèles.
CarAppService
CarAppService est une classe Service abstraite que votre application doit implémenter et exporter pour que l'hôte puisse la découvrir et la gérer. Le CarAppService de votre application est chargé de vérifier qu'une connexion hôte est fiable à l'aide de createHostValidator, puis de fournir des instances Session pour chaque connexion à l'aide de onCreateSession.
Session

Session est une classe abstraite que votre application doit implémenter et renvoyer à l'aide de CarAppService.onCreateSession. Il sert de point d'entrée pour afficher des informations sur l'écran de la voiture. Elle dispose d'un cycle de vie qui informe l'état actuel de votre application sur l'écran de la voiture, par exemple quand votre application est visible ou masquée.

Lorsqu'un Session est démarré, par exemple lors du premier lancement de l'application, l'hôte demande à l'Screen initiale de s'afficher à l'aide de la méthode onCreateScreen.

Installer la bibliothèque d'applications pour voitures

Consultez la page des versions de la bibliothèque Jetpack pour savoir comment l'ajouter à votre application.

Configurer les fichiers manifestes de votre application

Avant de pouvoir créer votre application pour voitures, configurez les fichiers manifestes de votre application comme suit.

Déclarer votre CarAppService

L'hôte se connecte à votre application via votre implémentation CarAppService. Vous déclarez ce service dans votre fichier manifeste pour permettre à l'hôte de découvrir votre application et de s'y connecter.

Vous devez également déclarer la catégorie de votre application dans l'élément <category> du filtre d'intent de votre application. Consultez la liste des catégories d'applications compatibles pour connaître les valeurs autorisées pour cet élément.

L'extrait de code suivant montre comment déclarer un service d'application pour voitures pour une application de point d'intérêt dans votre fichier manifeste:

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService"/>
        <category android:name="androidx.car.app.category.POI"/>
      </intent-filter>
    </service>

    ...
<application>

Catégories d'applications compatibles

Déclarez la catégorie de votre application en ajoutant une ou plusieurs des valeurs de catégorie suivantes au filtre d'intent lorsque vous déclarez votre CarAppService, comme décrit dans la section précédente:

  • androidx.car.app.category.NAVIGATION: application qui fournit des itinéraires de navigation détaillés. Consultez Créer des applications de navigation pour voitures pour obtenir de la documentation supplémentaire sur cette catégorie.
  • androidx.car.app.category.POI: application fournissant des fonctionnalités permettant de trouver des points d'intérêt tels que des places de parking, des bornes de recharge et des stations-service. Consultez Créer des applications de point d'intérêt pour voitures pour obtenir de la documentation supplémentaire sur cette catégorie.
  • androidx.car.app.category.IOT: application permettant aux utilisateurs d'effectuer des actions pertinentes sur des appareils connectés depuis la voiture. Consultez Compiler des applications IoT pour les voitures pour obtenir de la documentation supplémentaire sur cette catégorie.

Consultez la section Qualité des applications automobiles Android pour obtenir une description détaillée de chaque catégorie et des critères d'application des applications.

Spécifier le nom et l'icône de l'application

Vous devez spécifier un nom et une icône d'application que l'hôte peut utiliser pour représenter votre application dans l'UI du système.

Vous pouvez spécifier le nom et l'icône de l'application pour la représenter à l'aide des attributs label et icon de votre CarAppService:

...
<service
   android:name=".MyCarAppService"
   android:exported="true"
   android:label="@string/my_app_name"
   android:icon="@drawable/my_app_icon">
   ...
</service>
...

Si le libellé ou l'icône ne sont pas déclarés dans l'élément <service>, l'hôte utilise les valeurs spécifiées pour l'élément <application>.

Définir un thème personnalisé

Pour définir un thème personnalisé pour votre application pour voitures, ajoutez un élément <meta-data> dans votre fichier manifeste, comme suit:

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Ensuite, déclarez votre ressource de style afin de définir les attributs suivants pour votre thème d'application automobile personnalisé:

<resources>
  <style name="MyCarAppTheme">
    <item name="carColorPrimary">@layout/my_primary_car_color</item>
    <item name="carColorPrimaryDark">@layout/my_primary_dark_car_color</item>
    <item name="carColorSecondary">@layout/my_secondary_car_color</item>
    <item name="carColorSecondaryDark">@layout/my_secondary_dark_car_color</item>
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

Niveau d'API de Car App

La bibliothèque Car App définit ses propres niveaux d'API afin que vous puissiez savoir quelles fonctionnalités de la bibliothèque sont compatibles avec l'hôte du modèle sur un véhicule. Pour récupérer le niveau d'API de Car App le plus élevé accepté par un hôte, utilisez la méthode getCarAppApiLevel().

Déclarez le niveau d'API minimal de Car App compatible avec votre application dans le fichier AndroidManifest.xml:

<manifest ...>
    <application ...>
        <meta-data
            android:name="androidx.car.app.minCarApiLevel"
            android:value="1"/>
    </application>
</manifest>

Consultez la documentation sur l'annotation RequiresCarApi pour savoir comment maintenir la rétrocompatibilité et déclarer le niveau d'API minimal requis pour utiliser une fonctionnalité. Pour savoir quel niveau d'API est requis pour utiliser une certaine fonctionnalité de la bibliothèque Car App, consultez la documentation de référence sur CarAppApiLevels.

Créer votre CarAppService et votre session

Votre application doit étendre la classe CarAppService et implémenter sa méthode onCreateSession, qui renvoie une instance Session correspondant à la connexion actuelle à l'hôte:

Kotlin

class HelloWorldService : CarAppService() {
    ...
    override fun onCreateSession(): Session {
        return HelloWorldSession()
    }
    ...
}

Java

public final class HelloWorldService extends CarAppService {
    ...
    @Override
    @NonNull
    public Session onCreateSession() {
        return new HelloWorldSession();
    }
    ...
}

L'instance Session est chargée de renvoyer l'instance Screen à utiliser lors du premier démarrage de l'application:

Kotlin

class HelloWorldSession : Session() {
    ...
    override fun onCreateScreen(intent: Intent): Screen {
        return HelloWorldScreen(carContext)
    }
    ...
}

Java

public final class HelloWorldSession extends Session {
    ...
    @Override
    @NonNull
    public Screen onCreateScreen(@NonNull Intent intent) {
        return new HelloWorldScreen(getCarContext());
    }
    ...
}

Pour gérer les cas où votre application pour voitures doit démarrer à partir d'un écran autre que l'écran d'accueil ou l'écran de destination de votre application, par exemple pour gérer les liens profonds, vous pouvez précharger une pile "Retour" d'écrans à l'aide de ScreenManager.push avant de revenir à onCreateScreen. Cette approche permet aux utilisateurs de revenir aux écrans précédents à partir du premier écran affiché par votre application.

Créer votre écran de démarrage

Pour créer les écrans affichés par votre application, définissez des classes qui étendent la classe Screen et implémentez sa méthode onGetTemplate, qui renvoie l'instance Template représentant l'état de l'interface utilisateur à afficher sur l'écran de la voiture.

L'extrait de code suivant montre comment déclarer un Screen qui utilise un modèle PaneTemplate pour afficher une chaîne "Hello world!" simple:

Kotlin

class HelloWorldScreen(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()
    }
}

Java

public class HelloWorldScreen extends Screen {
    @NonNull
    @Override
    public Template onGetTemplate() {
        Row row = new Row.Builder().setTitle("Hello world!").build();
        Pane pane = new Pane.Builder().addRow(row).build();
        return new PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build();
    }
}

La classe CarContext

La classe CarContext est une sous-classe ContextWrapper accessible à vos instances Session et Screen. Il permet d'accéder aux services du véhicule, tels que ScreenManager pour gérer la pile d'écran, AppManager pour les fonctionnalités générales liées à l'application, comme l'accès à l'objet Surface pour dessiner la carte de votre application de navigation et à l'application NavigationManager utilisée par les applications de navigation détaillée pour communiquer les métadonnées de l'hôte et les autres événements de navigation.

Consultez Accéder aux modèles de navigation pour obtenir la liste complète des fonctionnalités de bibliothèque disponibles pour les applications de navigation.

CarContext offre également d'autres fonctionnalités, telles que la possibilité de charger des ressources drawable à l'aide de la configuration de l'écran de la voiture, de démarrer une application dans la voiture à l'aide d'intents et de signaler si votre application de navigation doit afficher sa carte en mode sombre.

Implémenter la navigation à l'écran

Les applications présentent souvent un certain nombre d'écrans différents, chacun utilisant différents modèles que l'utilisateur peut parcourir lorsqu'il interagit avec l'interface affichée à l'écran.

La classe ScreenManager fournit une pile d'écrans que vous pouvez utiliser pour transférer des écrans qui s'affichent automatiquement lorsque l'utilisateur sélectionne un bouton "Retour" sur l'écran de la voiture ou utilise le bouton physique "Retour" disponible dans certaines voitures.

L'extrait de code suivant montre comment ajouter une action "Retour" à un modèle de message ainsi qu'une action qui déclenche un nouvel écran lorsque l'utilisateur le sélectionne:

Kotlin

val template = MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener { screenManager.push(NextScreen(carContext)) }
            .build())
    .build()

Java

MessageTemplate template = new MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        new Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener(
                () -> getScreenManager().push(new NextScreen(getCarContext())))
            .build())
    .build();

L'objet Action.BACK est un Action standard qui appelle automatiquement ScreenManager.pop. Ce comportement peut être ignoré à l'aide de l'instance OnBackPressedDispatcher disponible à partir de CarContext.

Pour vous assurer que l'application peut être utilisée en toute sécurité pendant la conduite, la pile d'écrans peut avoir une profondeur maximale de cinq écrans. Pour en savoir plus, consultez la section Restrictions liées aux modèles.

Actualiser le contenu d'un modèle

Votre application peut demander l'annulation du contenu d'un Screen en appelant la méthode Screen.invalidate. L'hôte rappelle ensuite la méthode Screen.onGetTemplate de votre application pour récupérer le modèle avec le nouveau contenu.

Lors de l'actualisation d'un Screen, il est important de comprendre le contenu spécifique du modèle pouvant être mis à jour afin que l'hôte ne comptabilise pas le nouveau modèle dans le quota de modèles. Pour en savoir plus, consultez la section Restrictions liées aux modèles.

Nous vous recommandons de structurer vos écrans de manière à établir un mappage un à un entre un élément Screen et le type de modèle renvoyé via son implémentation onGetTemplate.

Interagir avec l'utilisateur

Votre application peut interagir avec l'utilisateur à l'aide de modèles semblables à ceux d'une application mobile.

Gérer les entrées utilisateur

Votre application peut répondre aux entrées utilisateur en transmettant les écouteurs appropriés aux modèles qui les prennent en charge. L'extrait de code suivant montre comment créer un modèle Action qui définit un élément OnClickListener qui rappelle une méthode définie par le code de votre application:

Kotlin

val action = Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(::onClickNavigate)
    .build()

Java

Action action = new Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(this::onClickNavigate)
    .build();

La méthode onClickNavigate peut ensuite démarrer l'application de navigation par défaut pour voiture à l'aide de la méthode CarContext.startCarApp:

Kotlin

private fun onClickNavigate() {
    val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address))
    carContext.startCarApp(intent)
}

Java

private void onClickNavigate() {
    Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address));
    getCarContext().startCarApp(intent);
}

Pour en savoir plus sur le démarrage d'applications, y compris sur le format de l'intent ACTION_NAVIGATE, consultez la section Démarrer une application pour voiture avec un intent.

Certaines actions, telles que celles qui nécessitent d'inciter l'utilisateur à poursuivre l'interaction sur ses appareils mobiles, ne sont autorisées que lorsque la voiture est à l'arrêt. Vous pouvez utiliser ParkedOnlyOnClickListener pour implémenter ces actions. Si la voiture n'est pas garée, l'hôte indique à l'utilisateur que l'action n'est pas autorisée dans ce cas. Si la voiture est garée, le code s'exécute normalement. L'extrait de code suivant montre comment utiliser ParkedOnlyOnClickListener pour ouvrir un écran de paramètres sur l'appareil mobile:

Kotlin

val row = Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone))
    .build()

Java

Row row = new Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone))
    .build();

Afficher les notifications

Les notifications envoyées à l'appareil mobile ne s'affichent sur l'écran de la voiture que si elles sont accompagnées d'un élément CarAppExtender. Certains attributs de notification, tels que le titre du contenu, le texte, l'icône et les actions, peuvent être définis dans CarAppExtender, remplaçant les attributs de la notification lorsqu'ils apparaissent sur l'écran de la voiture.

L'extrait de code suivant montre comment envoyer à l'écran de la voiture une notification dont le titre est différent de celui affiché sur l'appareil mobile:

Kotlin

val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build()

Java

Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        new CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build();

Les notifications peuvent affecter les parties suivantes de l'interface utilisateur:

  • Une notification prioritaire peut s'afficher à l'intention de l'utilisateur.
  • Une entrée peut être ajoutée dans le centre de notifications, éventuellement avec un badge visible dans le rail.
  • Pour les applications de navigation, la notification peut s'afficher dans le widget de rail, comme décrit dans la section Notifications de navigation détaillée.

Vous pouvez choisir comment configurer les notifications de votre application pour qu'elles affectent chacun de ces éléments de l'interface utilisateur en utilisant la priorité de la notification, comme décrit dans la documentation de CarAppExtender.

Si NotificationCompat.Builder.setOnlyAlertOnce est appelé avec la valeur true, une notification à priorité élevée ne s'affiche qu'une seule fois en tant que notification prioritaire.

Pour en savoir plus sur la conception des notifications de votre application pour voitures, consultez le guide de la conception de Google pour la conduite concernant les notifications.

Afficher les toasts

Votre application peut afficher un toast à l'aide de CarToast, comme indiqué dans cet extrait:

Kotlin

CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()

Java

CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();

Demander des autorisations

Si votre application a besoin d'accéder à des données ou à des actions limitées, telles que la localisation, les règles standards des autorisations Android s'appliquent. Pour demander une autorisation, vous pouvez utiliser la méthode CarContext.requestPermissions().

L'avantage d'utiliser CarContext.requestPermissions(), par rapport aux API Android standards, est que vous n'avez pas besoin de lancer votre propre Activity pour créer la boîte de dialogue des autorisations. De plus, vous pouvez utiliser le même code sur Android Auto et sur Android Automotive OS, plutôt que d'avoir à créer des flux dépendants de la plate-forme.

Appliquer un style à la boîte de dialogue des autorisations sur Android Auto

Sous Android Auto, la boîte de dialogue des autorisations de l'utilisateur s'affiche sur le téléphone. Par défaut, aucun arrière-plan ne s'affiche derrière la boîte de dialogue. Pour définir un arrière-plan personnalisé, déclarez un thème d'application pour voitures dans votre fichier AndroidManifest.xml et définissez l'attribut carPermissionActivityLayout pour ce thème.

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

Ensuite, définissez l'attribut carPermissionActivityLayout pour le thème de votre application pour voitures :

<resources>
  <style name="MyCarAppTheme">
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

Démarrer une application pour voiture avec un intent

Vous pouvez appeler la méthode CarContext.startCarApp pour effectuer l'une des actions suivantes:

L'exemple suivant montre comment créer une notification avec une action qui ouvre votre application avec un écran affichant les détails d'une réservation de stationnement. Vous étendez l'instance de notification avec un intent de contenu contenant un PendingIntent encapsulant un intent explicite pour l'action de votre application:

Kotlin

val notification = notificationBuilder
    ...
    .extend(
        CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(ComponentName(context, MyNotificationReceiver::class.java)),
                    0))
            .build())

Java

Notification notification = notificationBuilder
    ...
    .extend(
        new CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    new Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(new ComponentName(context, MyNotificationReceiver.class)),
                    0))
            .build());

Votre application doit également déclarer un BroadcastReceiver qui est appelé pour traiter l'intent lorsque l'utilisateur sélectionne l'action dans l'interface de notification et appelle CarContext.startCarApp avec un intent incluant l'URI de données:

Kotlin

class MyNotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val intentAction = intent.action
        if (ACTION_VIEW_PARKING_RESERVATION == intentAction) {
            CarContext.startCarApp(
                intent,
                Intent(Intent.ACTION_VIEW)
                    .setComponent(ComponentName(context, MyCarAppService::class.java))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)))
        }
    }
}

Java

public class MyNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String intentAction = intent.getAction();
        if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) {
            CarContext.startCarApp(
                intent,
                new Intent(Intent.ACTION_VIEW)
                    .setComponent(new ComponentName(context, MyCarAppService.class))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)));
        }
    }
}

Enfin, la méthode Session.onNewIntent de votre application gère cet intent en déployant l'écran de réservation de stationnement sur la pile, s'il n'est pas déjà en haut:

Kotlin

override fun onNewIntent(intent: Intent) {
    val screenManager = carContext.getCarService(ScreenManager::class.java)
    val uri = intent.data
    if (uri != null
        && MY_URI_SCHEME == uri.scheme
        && MY_URI_HOST == uri.schemeSpecificPart
        && ACTION_VIEW_PARKING_RESERVATION == uri.fragment
    ) {
        val top = screenManager.top
        if (top !is ParkingReservationScreen) {
            screenManager.push(ParkingReservationScreen(carContext))
        }
    }
}

Java

@Override
public void onNewIntent(@NonNull Intent intent) {
    ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
    Uri uri = intent.getData();
    if (uri != null
        && MY_URI_SCHEME.equals(uri.getScheme())
        && MY_URI_HOST.equals(uri.getSchemeSpecificPart())
        && ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment())
    ) {
        Screen top = screenManager.getTop();
        if (!(top instanceof ParkingReservationScreen)) {
            screenManager.push(new ParkingReservationScreen(getCarContext()));
        }
    }
}

Consultez la section Afficher les notifications pour savoir comment gérer les notifications de l'application pour voitures.

Restrictions des modèles

L'hôte limite à cinq maximum le nombre de modèles à afficher pour une tâche donnée, le dernier modèle devant être de l'un des types suivants:

Notez que cette limite s'applique au nombre de modèles, et non au nombre d'instances Screen dans la pile. Par exemple, si une application envoie deux modèles à l'écran A, puis transmet l'écran B, elle peut désormais envoyer trois autres modèles. Si chaque écran est structuré de manière à envoyer un seul modèle, l'application peut également transférer cinq instances d'écran vers la pile ScreenManager.

Ces restrictions existent dans des cas particuliers: actualisations de modèles et opérations de retour et de réinitialisation.

Actualisations de modèles

Certaines mises à jour de contenu ne sont pas comptabilisées dans le nombre maximal de modèles. En général, si une application transmet un nouveau modèle du même type et contenant le même contenu principal que le modèle précédent, le nouveau modèle n'est pas comptabilisé dans le quota. Par exemple, la modification de l'état d'activation d'une ligne dans un objet ListTemplate n'est pas comptabilisée dans le quota. Consultez la documentation des modèles individuels pour en savoir plus sur les types de mises à jour de contenu pouvant être considérées comme une actualisation.

Opérations de retour

Pour activer les sous-flux dans une tâche, l'hôte détecte lorsqu'une application génère un Screen à partir de la pile ScreenManager et met à jour le quota restant en fonction du nombre de modèles rétrogradés.

Par exemple, si l'application envoie deux modèles sur l'écran A, puis transmet l'écran B et envoie deux autres modèles, elle dispose d'un quota restant. Si l'application revient ensuite à l'écran A, l'hôte réinitialise le quota à trois, car l'application a reculé de deux modèles.

Notez que, lorsque vous revenez à un écran, une application doit envoyer un modèle du même type que le dernier envoyé par cet écran. L'envoi de tout autre type de modèle entraîne une erreur. Toutefois, tant que le type reste le même lors d'une opération secondaire, une application peut modifier librement le contenu du modèle sans affecter le quota.

Opérations de réinitialisation

Certains modèles ont une sémantique spéciale qui indique la fin d'une tâche. Par exemple, NavigationTemplate est une vue qui doit rester à l'écran et être actualisée avec de nouvelles instructions détaillées destinées à l'utilisateur. Lorsqu'il atteint l'un de ces modèles, l'hôte réinitialise le quota du modèle, en traitant ce modèle comme s'il s'agissait de la première étape d'une nouvelle tâche. Cela permet à l'application de commencer une nouvelle tâche. Consultez la documentation des modèles individuels pour savoir lesquels déclenchent une réinitialisation sur l'hôte.

Si l'hôte reçoit un intent pour démarrer l'application à partir d'une action de notification ou du lanceur d'applications, le quota est également réinitialisé. Ce mécanisme permet à une application de lancer un nouveau flux de tâches à partir des notifications, et cela reste vrai même si une application est déjà liée et au premier plan.

Consultez la section Afficher les notifications pour savoir comment afficher les notifications de votre application sur l'écran de la voiture. Consultez la section Démarrer une application pour voitures avec un intent pour savoir comment démarrer votre application à partir d'une action de notification.

API Connection

Vous pouvez déterminer si votre application s'exécute sur Android Auto ou sur Android Automotive OS en utilisant l'API CarConnection pour récupérer les informations de connexion au moment de l'exécution.

Par exemple, dans le Session de votre application pour voitures, initialisez un CarConnection et abonnez-vous aux mises à jour de LiveData:

Kotlin

CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)

Java

new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);

Dans l'observateur, vous pouvez ensuite réagir aux changements d'état de la connexion:

Kotlin

fun onConnectionStateUpdated(connectionState: Int) {
  val message = when(connectionState) {
    CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
    CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS"
    CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
    else -> "Unknown car connection type"
  }
  CarToast.makeText(carContext, message, CarToast.LENGTH_SHORT).show()
}

Java

private void onConnectionStateUpdated(int connectionState) {
  String message;
  switch(connectionState) {
    case CarConnection.CONNECTION_TYPE_NOT_CONNECTED:
      message = "Not connected to a head unit";
      break;
    case CarConnection.CONNECTION_TYPE_NATIVE:
      message = "Connected to Android Automotive OS";
      break;
    case CarConnection.CONNECTION_TYPE_PROJECTION:
      message = "Connected to Android Auto";
      break;
    default:
      message = "Unknown car connection type";
      break;
  }
  CarToast.makeText(getCarContext(), message, CarToast.LENGTH_SHORT).show();
}

API Constraints

Différentes voitures peuvent permettre d'afficher un nombre différent d'instances Item à la fois pour l'utilisateur. Utilisez ConstraintManager pour vérifier la limite de contenu au moment de l'exécution et définir le nombre approprié d'éléments dans vos modèles.

Commencez par obtenir un ConstraintManager à partir de CarContext:

Kotlin

val manager = carContext.getCarService(ConstraintManager::class.java)

Java

ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);

Vous pouvez ensuite interroger l'objet ConstraintManager récupéré pour connaître la limite de contenu appropriée. Par exemple, pour obtenir le nombre d'éléments pouvant être affichés dans une grille, appelez getContentLimit avec CONTENT_LIMIT_TYPE_GRID:

Kotlin

val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)

Java

int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);

Ajouter un flux de connexion

Si votre application offre une expérience de connexion aux utilisateurs, vous pouvez utiliser des modèles tels que SignInTemplate et LongMessageTemplate avec le niveau d'API 2 ou supérieur de Car App pour gérer la connexion à votre application sur l'unité principale de la voiture.

Pour créer un SignInTemplate, définissez un SignInMethod. La bibliothèque Car App accepte actuellement les méthodes de connexion suivantes:

  • InputSignInMethod pour la connexion par nom d'utilisateur/mot de passe.
  • PinSignInMethod pour la connexion par code, où l'utilisateur associe son compte à partir de son téléphone à l'aide d'un code affiché sur l'unité principale.
  • ProviderSignInMethod pour la connexion à un fournisseur, comme Google Sign-In et One Tap.
  • QRCodeSignInMethod pour la connexion par code QR : l'utilisateur scanne un code QR pour se connecter sur son téléphone. Cette fonctionnalité est disponible pour les API Car au niveau d'API 4 ou supérieur.

Par exemple, pour implémenter un modèle qui collecte le mot de passe de l'utilisateur, commencez par créer un InputCallback afin de traiter et de valider les entrées utilisateur:

Kotlin

val callback = object : InputCallback {
    override fun onInputSubmitted(text: String) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    override fun onInputTextChanged(text: String) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
}

Java

InputCallback callback = new InputCallback() {
    @Override
    public void onInputSubmitted(@NonNull String text) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    @Override
    public void onInputTextChanged(@NonNull String text) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
};

Un élément InputCallback est requis pour le champ InputSignInMethod Builder.

Kotlin

val passwordInput = InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build()

Java

InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build();

Enfin, utilisez votre nouveau InputSignInMethod pour créer un SignInTemplate.

Kotlin

SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build()

Java

new SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build();

Utiliser AccountManager

Les applications Android Automotive OS avec authentification doivent utiliser AccountManager pour les raisons suivantes :

  • Meilleure expérience utilisateur et gestion simplifiée du compte: les utilisateurs peuvent facilement gérer tous leurs comptes à partir du menu "Comptes" dans les paramètres système, y compris la connexion et la déconnexion.
  • Expériences des invités: les voitures étant des appareils partagés, les OEM peuvent activer l'expérience Invité dans le véhicule, lorsque l'ajout de compte n'est pas possible.

Ajouter des variantes de chaîne de texte

La taille de l'écran peut varier en fonction de la taille de l'écran. Avec les niveaux d'API 2 et supérieurs de Car App, vous pouvez spécifier plusieurs variantes d'une chaîne de texte pour les adapter au mieux à l'écran. Pour savoir où les variantes de texte sont acceptées, recherchez les modèles et les composants qui utilisent un élément CarText.

Vous pouvez ajouter des variantes de chaîne de texte à un CarText avec la méthode CarText.Builder.addVariant():

Kotlin

val itemTitle = CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build()

Java

CarText itemTitle = new CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build();

Vous pouvez ensuite utiliser ce CarText, par exemple comme texte principal d'un GridItem.

Kotlin

GridItem.Builder()
    .addTitle(itemTitle)
    ...
    .build()

Java

new GridItem.Builder()
    .addTitle(itemTitle)
    ...
    build();

Ajoutez les chaînes par ordre décroissant, de la plus longue à la plus courte, par exemple. L'hôte choisit la chaîne de longueur appropriée en fonction de la quantité d'espace disponible sur l'écran de la voiture.

Ajouter des carIcons intégrés pour les lignes

Vous pouvez ajouter des icônes intégrées au texte pour enrichir l'attrait visuel de votre application à l'aide de CarIconSpan. Pour en savoir plus sur la création de ces segments, consultez la documentation concernant CarIconSpan.create. Consultez la section sur l'ajout de styles spantastic avec des objets Span pour obtenir un aperçu du fonctionnement des styles de texte avec des objets Span.

Kotlin

  
val rating = SpannableString("Rating: 4.5 stars")
rating.setSpan(
    CarIconSpan.create(
        // Create a CarIcon with an image of four and a half stars
        CarIcon.Builder(...).build(),
        // Align the CarIcon to the baseline of the text
        CarIconSpan.ALIGN_BASELINE
    ),
    // The start index of the span (index of the character '4')
    8,
    // The end index of the span (index of the last 's' in "stars")
    16,
    Spanned.SPAN_INCLUSIVE_INCLUSIVE
)

val row = Row.Builder()
    ...
    .addText(rating)
    .build()
  
  

Java

  
SpannableString rating = new SpannableString("Rating: 4.5 stars");
rating.setSpan(
        CarIconSpan.create(
                // Create a CarIcon with an image of four and a half stars
                new CarIcon.Builder(...).build(),
                // Align the CarIcon to the baseline of the text
                CarIconSpan.ALIGN_BASELINE
        ),
        // The start index of the span (index of the character '4')
        8,
        // The end index of the span (index of the last 's' in "stars")
        16,
        Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
Row row = new Row.Builder()
        ...
        .addText(rating)
        .build();
  
  

API Car Hardware

À partir du niveau d'API 3 de Car App, la bibliothèque Car App contient des API que vous pouvez utiliser pour accéder aux propriétés et aux capteurs du véhicule.

Exigences

Pour utiliser les API avec Android Auto, commencez par ajouter une dépendance sur androidx.car.app:app-projected au fichier build.gradle de votre module Android Auto. Pour Android Automotive OS, ajoutez une dépendance sur androidx.car.app:app-automotive au fichier build.gradle de votre module Android Automotive OS.

En outre, dans votre fichier AndroidManifest.xml, vous devez déclarer les autorisations pertinentes nécessaires pour demander les données de voiture que vous souhaitez utiliser. Notez que ces autorisations doivent également vous être accordées par l'utilisateur. Vous pouvez utiliser le même code sur Android Auto et sur Android Automotive OS, plutôt que d'avoir à créer des flux dépendant de la plate-forme. Cependant, les autorisations nécessaires sont différentes.

Infos voiture

Ce tableau décrit les propriétés mises en évidence par les API CarInfo et les autorisations que vous devez demander pour les utiliser:

Méthodes Propriétés Autorisations Android Auto Autorisations Android Automotive OS
fetchModel Marque, modèle, année android.car.permission.CAR_INFO
fetchEnergyProfile Types de connecteurs de VE, types de carburant com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_INFO
addTollListener
removeTollListener
État de la carte à péage, type de carte à péage
addEnergyLevelListener
removeEnergyLevelListener
Niveau de la batterie, niveau de carburant, niveau de carburant faible, autonomie restante com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_ENERGY,
android.car.permission.CAR_ENERGY_PORTS,
android.car.permission.READ_CAR_DISPLAY_UNITS
addSpeedListener
removeSpeedListener
Vitesse brute, vitesse d'affichage (affichée sur l'écran de la voiture) com.google.android.gms.permission.CAR_SPEED android.car.permission.CAR_SPEED,
android.car.permission.READ_CAR_DISPLAY_UNITS
addMileageListener
removeMileageListener
Distance kilométrique com.google.android.gms.permission.CAR_MILEAGE Ces données ne sont pas disponibles sur Android Automotive OS pour les applications installées depuis le Play Store.

Par exemple, pour obtenir la plage restante, instanciez un objet CarInfo, puis créez et enregistrez une OnCarDataAvailableListener:

Kotlin

val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo

val listener = OnCarDataAvailableListener<EnergyLevel> { data ->
    if (data.rangeRemainingMeters.status == CarValue.STATUS_SUCCESS) {
      val rangeRemaining = data.rangeRemainingMeters.value
    } else {
      // Handle error
    }
  }

carInfo.addEnergyLevelListener(carContext.mainExecutor, listener)
…
// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener)

Java

CarInfo carInfo = getCarContext().getCarService(CarHardwareManager.class).getCarInfo();

OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
  if(data.getRangeRemainingMeters().getStatus() == CarValue.STATUS_SUCCESS) {
    float rangeRemaining = data.getRangeRemainingMeters().getValue();
  } else {
    // Handle error
  }
};

carInfo.addEnergyLevelListener(getCarContext().getMainExecutor(), listener);
…
// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener);

Ne partez pas du principe que les données de la voiture sont disponibles en permanence. Si vous obtenez une erreur, vérifiez l'état de la valeur demandée afin de mieux comprendre pourquoi les données demandées n'ont pas pu être récupérées. Consultez la documentation de référence pour obtenir la définition complète de la classe CarInfo.

Capteurs de voiture

La classe CarSensors vous donne accès à l'accéléromètre, au gyroscope, à la boussole et aux données de localisation du véhicule. La disponibilité de ces valeurs peut dépendre de l'OEM. Le format des données de l'accéléromètre, du gyroscope et de la boussole est le même que celui de l'API SensorManager. Par exemple, pour vérifier la direction du véhicule:

Kotlin

val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors

val listener = OnCarDataAvailableListener<Compass> { data ->
    if (data.orientations.status == CarValue.STATUS_SUCCESS) {
      val orientation = data.orientations.value
    } else {
      // Data not available, handle error
    }
  }

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, listener)
…
// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener)

Java

CarSensors carSensors = getCarContext().getCarService(CarHardwareManager.class).getCarSensors();

OnCarDataAvailableListener<Compass> listener = (data) -> {
  if (data.getOrientations().getStatus() == CarValue.STATUS_SUCCESS) {
    List<Float> orientations = data.getOrientations().getValue();
  } else {
    // Data not available, handle error
  }
};

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, getCarContext().getMainExecutor(),
    listener);
…
// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener);

Pour accéder aux données de localisation de la voiture, vous devez également déclarer et demander l'autorisation android.permission.ACCESS_FINE_LOCATION.

Test

Pour simuler des données de capteurs lors des tests sur Android Auto, consultez les sections Capteurs et Configuration des capteurs du guide de l'unité principale pour ordinateur. Pour simuler des données de capteurs lors des tests sur Android Automotive OS, consultez la section Émuler l'état du matériel du guide de l'émulateur Android Automotive OS.

Cycles de vie de CarAppService, de session et d'écran

Les classes Session et Screen implémentent l'interface LifecycleOwner. Lorsque l'utilisateur interagit avec l'application, les rappels de cycle de vie de vos objets Session et Screen sont appelés, comme décrit dans les schémas suivants.

Cycles de vie d'un CarAppService et d'une session

Figure 1. Cycle de vie d'une Session.

Pour en savoir plus, consultez la documentation de la méthode Session.getLifecycle.

Cycle de vie d'un écran

Figure 2. Cycle de vie d'une Screen.

Pour en savoir plus, consultez la documentation de la méthode Screen.getLifecycle.

Enregistrer depuis le micro de la voiture

À l'aide du CarAppService de votre application et de l'API CarAudioRecord, vous pouvez autoriser votre application à accéder au micro de la voiture de l'utilisateur. Les utilisateurs doivent autoriser votre application à accéder au micro de la voiture. Votre application peut enregistrer et traiter les entrées de l'utilisateur.

Autorisation d'enregistrement

Avant d'enregistrer du contenu audio, vous devez d'abord déclarer l'autorisation d'enregistrer dans votre AndroidManifest.xml et demander à l'utilisateur de l'accorder.

<manifest ...>
   ...
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   ...
</manifest>

Vous devez demander l'autorisation d'enregistrer au moment de l'exécution. Consultez la section Demander des autorisations pour savoir comment demander une autorisation dans votre application pour voitures.

Enregistrer un fichier audio

Une fois que l'utilisateur a donné l'autorisation d'enregistrer, vous pouvez enregistrer l'audio et le traiter.

Kotlin

val carAudioRecord = CarAudioRecord.create(carContext)
        carAudioRecord.startRecording()

        val data = ByteArray(CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE)
        while(carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording()
 

Java

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        carAudioRecord.startRecording();

        byte[] data = new byte[CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE];
        while (carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording();
 

Priorité audio

Lorsque vous enregistrez à partir du micro de la voiture, commencez par obtenir la priorité audio pour vous assurer que tout contenu multimédia en cours est arrêté. Si vous perdez la sélection audio, arrêtez l'enregistrement.

Voici un exemple de la façon d'obtenir la priorité audio:

Kotlin

 
val carAudioRecord = CarAudioRecord.create(carContext)
        
        // Take audio focus so that user's media is not recorded
        val audioAttributes = AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
            // Use the most appropriate usage type for your use case
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
            .build()
        
        val audioFocusRequest =
            AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                .setAudioAttributes(audioAttributes)
                .setOnAudioFocusChangeListener { state: Int ->
                    if (state == AudioManager.AUDIOFOCUS_LOSS) {
                        // Stop recording if audio focus is lost
                        carAudioRecord.stopRecording()
                    }
                }
                .build()
        
        if (carContext.getSystemService(AudioManager::class.java)
                .requestAudioFocus(audioFocusRequest)
            != AudioManager.AUDIOFOCUS_REQUEST_GRANTED
        ) {
            // Don't record if the focus isn't granted
            return
        }
        
        carAudioRecord.startRecording()
        // Process the audio and abandon the AudioFocusRequest when done

Java

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        // Take audio focus so that user's media is not recorded
        AudioAttributes audioAttributes =
                new AudioAttributes.Builder()
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        // Use the most appropriate usage type for your use case
                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
                        .build();

        AudioFocusRequest audioFocusRequest =
                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                        .setAudioAttributes(audioAttributes)
                        .setOnAudioFocusChangeListener(state -> {
                            if (state == AudioManager.AUDIOFOCUS_LOSS) {
                                // Stop recording if audio focus is lost
                                carAudioRecord.stopRecording();
                            }
                        })
                        .build();

        if (getCarContext().getSystemService(AudioManager.class).requestAudioFocus(audioFocusRequest)
                != AUDIOFOCUS_REQUEST_GRANTED) {
            // Don't record if the focus isn't granted
            return;
        }

        carAudioRecord.startRecording();
        // Process the audio and abandon the AudioFocusRequest when done
 

Bibliothèque de tests

La bibliothèque de tests Android for Cars fournit des classes auxiliaires que vous pouvez utiliser pour valider le comportement de votre application dans un environnement de test. Par exemple, SessionController vous permet de simuler une connexion à l'hôte, et de vérifier que les bons Screen et Template sont créés et renvoyés.

Reportez-vous à la section Exemples pour voir des exemples d'utilisation.

Signaler un problème avec la bibliothèque d'applications Android for Cars

Si vous rencontrez un problème avec la bibliothèque, signalez-le à l'aide de Google Issue Tracker. Veillez à fournir toutes les informations requises dans le modèle dédié.

Signaler un nouveau problème

Avant de signaler un nouveau problème, vérifiez s'il figure dans les notes de version de la bibliothèque ou s'il figure dans la liste des problèmes. Vous pouvez vous abonner et voter pour des problèmes en cliquant sur l'étoile correspondant à un problème dans l'outil de suivi. Pour en savoir plus, consultez S'abonner à un problème.