Créer des applications de chat pour Android Auto

Pour de nombreux conducteurs, il est important de rester connectés par le biais de messages. Grâce aux applications de chat, par exemple, un utilisateur peut être averti qu'il doit passer prendre son enfant à l'école ou que le lieu d'un rendez-vous a changé. Le framework Android permet aux applications de chat d'étendre leurs services à l'expérience de conduite à l'aide d'une interface utilisateur standard qui permet aux conducteurs de garder un œil sur la route.

Les applications qui prennent en charge la messagerie peuvent étendre leurs notifications pour permettre à Android Auto de les utiliser lorsqu'il est en cours d'exécution. Ces notifications, qui sont affichées dans Android Auto, permettent aux utilisateurs de lire les messages et d'y répondre dans une interface cohérente qui ne les distrait pas. De plus, lorsque vous utilisez l'API MessagingStyle, vous recevez des notifications de messages optimisées pour tous les appareils Android, y compris Android Auto. Ces optimisations incluent une UI spécialisée pour les notifications de messages, des animations améliorées et la prise en charge des images intégrées.

Ce guide vous explique comment étendre une application qui présente des messages à l'utilisateur et reçoit ses réponses, telles qu'une application de chat, afin de permettre l'affichage des messages et la réception des réponses sur un appareil Android Auto. Pour obtenir des conseils sur la conception, consultez la section Applications de chat sur le site dédié à la conception pour la conduite.

Premiers pas

Pour fournir un service de messagerie sur les appareils Android Auto, votre application doit déclarer la prise en charge d'Android Auto dans le fichier manifeste et doit être en mesure d'effectuer les opérations suivantes :

  • Créer et envoyer des objets NotificationCompat.MessagingStyle contenant des objets Action de type "Répondre" et "Marquer comme lu"
  • Gérer les réponses et le marquage d'une conversation comme lue avec un Service

Concepts et objets

Avant de commencer à concevoir votre application, il est important de comprendre comment Android Auto gère la messagerie.

Un fragment de communication est appelé message et est représenté par la classe MessagingStyle.Message. Un message comprend un expéditeur, un contenu et l'heure à laquelle il a été envoyé.

La communication entre les utilisateurs est appelée conversation et est représentée par un objet MessagingStyle. Une conversation, ou MessagingStyle, contient un titre et les messages. Elle indique également si elle est menée au sein d'un groupe.

Pour avertir les utilisateurs qu'une conversation a été mise à jour (un nouveau message est arrivé, par exemple), les applications publient une Notification sur le système Android. Cette Notification utilise l'objet MessagingStyle pour afficher l'UI spécifique à la messagerie dans le volet des notifications. La plate-forme Android transmet également cette Notification à Android Auto. Le MessagingStyle est extrait et utilisé pour publier une notification sur l'écran de la voiture.

Android Auto exige également que les applications ajoutent des objets Action à une Notification afin de permettre à l'utilisateur de répondre rapidement à un message ou de le marquer comme lu directement depuis le volet des notifications.

En résumé, une seule conversation est représentée par un objet Notification stylisé avec un objet MessagingStyle. MessagingStyle contient tous les messages de cette conversation dans un ou plusieurs objets MessagingStyle.Message. Pour que l'application soit compatible avec Android Auto, elle doit joindre des objets Action "Répondre" et "Marquer comme lu" à la Notification.

Flux de messagerie

Cette section décrit un flux de messagerie standard entre votre application et Android Auto.

  1. Votre application reçoit un message.
  2. Votre application génère une notification MessagingStyle avec des objets Action "Répondre" et "Marquer comme lu".
  3. Android Auto reçoit l'événement "nouvelle notification" du système Android et trouve le MessagingStyle, l'Action "Répondre" et l'Action "Marquer comme lu".
  4. Android Auto génère une notification et l'affiche dans la voiture.
  5. Si l'utilisateur appuie sur la notification sur l'écran de la voiture, Android Auto déclenche l'Action "Marquer comme lu".
    • En arrière-plan, votre application doit gérer cet événement "Marquer comme lu".
  6. Si l'utilisateur répond à la notification par commande vocale, Android Auto inclut une transcription de sa réponse dans l'Action "Répondre", puis la déclenche.
    • En arrière-plan, votre application doit gérer cet événement "Répondre".

Hypothèses préliminaires

Le but de cette page n'est pas de vous aider à créer une application de chat dans son intégralité. L'exemple de code ci-dessous contient certains des éléments dont votre application doit disposer avant que vous puissiez commencer à prendre en charge la messagerie avec Android Auto :

data class YourAppConversation(
        val id: Int,
        val title: String,
        val recipients: MutableList<YourAppUser>,
        val icon: Bitmap) {
    companion object {
        /** Fetches [YourAppConversation] by its [id]. */
        fun getById(id: Int): YourAppConversation = // ...
    }

    /** Replies to this conversation with the given [message]. */
    fun reply(message: String) {}

    /** Marks this conversation as read. */
    fun markAsRead() {}

    /** Retrieves all unread messages from this conversation. */
    fun getUnreadMessages(): List<YourAppMessage> { return /* ... */ }
}
data class YourAppUser(val id: Int, val name: String, val icon: Uri)
data class YourAppMessage(
    val id: Int,
    val sender: YourAppUser,
    val body: String,
    val timeReceived: Long)

Déclarer la prise en charge d'Android Auto

Quand Android Auto reçoit une notification d'une application de chat, il vérifie que l'application a déclaré la prise en charge d'Android Auto. Pour activer cette prise en charge, incluez l'entrée suivante dans le fichier manifeste de votre application :

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

Cette entrée de fichier manifeste fait référence à un autre fichier XML que vous devez créer avec le chemin suivant : YourAppProject/app/src/main/res/xml/automotive_app_desc.xml. Dans automotive_app_desc.xml, déclarez les fonctionnalités Android Auto prises en charge par votre application. Par exemple, pour déclarer la prise en charge des notifications, incluez l'extrait suivant :

<automotiveApp>
    <uses name="notification" />
</automotiveApp>

Si votre application peut être définie en tant que gestionnaire de SMS par défaut, veillez à inclure l'élément <uses> suivant. Si vous ne le faites pas, un gestionnaire par défaut intégré à Android Auto sera utilisé pour gérer les SMS/MMS entrants lorsque votre application est définie comme gestionnaire de SMS par défaut, ce qui peut entraîner des notifications en double.

<automotiveApp>
    ...
    <uses name="sms" />
</automotiveApp>

Importer la bibliothèque principale AndroidX

Pour créer des notifications à utiliser avec des appareils Android Auto, la bibliothèque principale AndroidX est requise. Pour l'importer dans votre projet, procédez comme suit :

  1. Dans le fichier build.gradle de premier niveau, incluez une dépendance dans le dépôt Maven de Google, comme illustré dans l'exemple suivant :

Groovy

allprojects {
    repositories {
        google()
    }
}

Kotlin

allprojects {
    repositories {
        google()
    }
}
  1. Dans le fichier build.gradle de votre module d'application, incluez la dépendance de la bibliothèque AndroidX Core, comme illustré dans l'exemple suivant :

Groovy

dependencies {
    // If your app is written in Java
    implementation 'androidx.core:core:1.12.0'

    // If your app is written in Kotlin
    implementation 'androidx.core:core-ktx:1.12.0'
}

Kotlin

dependencies {
    // If your app is written in Java
    implementation("androidx.core:core:1.12.0")

    // If your app is written in Kotlin
    implementation("androidx.core:core-ktx:1.12.0")
}

Gérer les actions des utilisateurs

Votre application de chat doit pouvoir gérer la mise à jour d'une conversation via une Action. Pour Android Auto, votre application doit gérer deux types d'objets Action : répondre et marquer comme lu. Nous vous recommandons de les gérer à l'aide d'un élément IntentService, qui offre la possibilité de gérer les appels potentiellement coûteux en arrière-plan, libérant ainsi le fil de discussion principal de l'application.

Définir des actions d'intent

Les actions Intent sont de simples chaînes qui identifient la finalité de l'Intent. Étant donné qu'un seul service peut gérer plusieurs types d'intents, il est plus facile de définir plusieurs chaînes d'action que de définir plusieurs composants IntentService.

Comme illustré dans l'exemple de code suivant, l'exemple d'application de chat de ce guide présente les deux types d'actions obligatoires : répondre et marquer comme lu.

private const val ACTION_REPLY = "com.example.REPLY"
private const val ACTION_MARK_AS_READ = "com.example.MARK_AS_READ"

Créer le service

Pour créer un service qui gère ces objets Action, vous avez besoin de l'ID de conversation (une structure de données arbitraire définie par votre application qui identifie la conversation) et d'une clé d'entrée distante (qui sera abordée plus en détail dans la suite de cette section). L'exemple de code suivant crée un service pour gérer les actions requises :

private const val EXTRA_CONVERSATION_ID_KEY = "conversation_id"
private const val REMOTE_INPUT_RESULT_KEY = "reply_input"

/**
 * An [IntentService] that handles reply and mark-as-read actions for
 * [YourAppConversation]s.
 */
class MessagingService : IntentService("MessagingService") {
    override fun onHandleIntent(intent: Intent?) {
        // Fetches internal data.
        val conversationId = intent!!.getIntExtra(EXTRA_CONVERSATION_ID_KEY, -1)

        // Searches the database for that conversation.
        val conversation = YourAppConversation.getById(conversationId)

        // Handles the action that was requested in the intent. The TODOs
        // are addressed in a later section.
        when (intent.action) {
            ACTION_REPLY -> TODO()
            ACTION_MARK_AS_READ -> TODO()
        }
    }
}

Pour associer ce service à votre application, vous devez également l'enregistrer dans le fichier manifeste de votre application, comme indiqué dans l'exemple suivant :

<application>
    <service android:name="com.example.MessagingService" />
    ...
</application>

Générer et gérer des intents

Il n'y a aucun moyen pour les autres applications, y compris pour Android Auto, d'obtenir l'Intent qui déclenche le MessagingService, car les Intent sont transmis à des applications externes via un PendingIntent. En raison de cette limitation, vous devez créer un objet RemoteInput pour permettre à ces autres applications de retransmettre le texte "Répondre" à votre application, comme illustré dans l'exemple suivant :

/**
 * Creates a [RemoteInput] that lets remote apps provide a response string
 * to the underlying [Intent] within a [PendingIntent].
 */
fun createReplyRemoteInput(context: Context): RemoteInput {
    // RemoteInput.Builder accepts a single parameter: the key to use to store
    // the response in.
    return RemoteInput.Builder(REMOTE_INPUT_RESULT_KEY).build()
    // Note that the RemoteInput has no knowledge of the conversation. This is
    // because the data for the RemoteInput is bound to the reply Intent using
    // static methods in the RemoteInput class.
}

/** Creates an [Intent] that handles replying to the given [appConversation]. */
fun createReplyIntent(
        context: Context, appConversation: YourAppConversation): Intent {
    // Creates the intent backed by the MessagingService.
    val intent = Intent(context, MessagingService::class.java)

    // Lets the MessagingService know this is a reply request.
    intent.action = ACTION_REPLY

    // Provides the ID of the conversation that the reply applies to.
    intent.putExtra(EXTRA_CONVERSATION_ID_KEY, appConversation.id)

    return intent
}

Dans la clause switch ACTION_REPLY de MessagingService, extrayez les informations qui figurent dans la réponse Intent, comme illustré dans l'exemple suivant :

ACTION_REPLY -> {
    // Extracts reply response from the intent using the same key that the
    // RemoteInput uses.
    val results: Bundle = RemoteInput.getResultsFromIntent(intent)
    val message = results.getString(REMOTE_INPUT_RESULT_KEY)

    // This conversation object comes from the MessagingService.
    conversation.reply(message)
}

L'élément Intent "Marquer comme lu" est géré de la même manière. Toutefois, il ne nécessite pas de RemoteInput, comme illustré dans l'exemple suivant :

/** Creates an [Intent] that handles marking the [appConversation] as read. */
fun createMarkAsReadIntent(
        context: Context, appConversation: YourAppConversation): Intent {
    val intent = Intent(context, MessagingService::class.java)
    intent.action = ACTION_MARK_AS_READ
    intent.putExtra(EXTRA_CONVERSATION_ID_KEY, appConversation.id)
    return intent
}

La clause Switch ACTION_MARK_AS_READ de MessagingService ne nécessite aucune autre logique, comme illustré dans l'exemple suivant :

// Marking as read has no other logic.
ACTION_MARK_AS_READ -> conversation.markAsRead()

Informer les utilisateurs de la réception de messages

Une fois la gestion des actions de conversation terminée, l'étape suivante consiste à générer des notifications conformes à Android Auto.

Créer des actions

Les objets Action peuvent être transmis à d'autres applications à l'aide d'une Notification pour déclencher des méthodes dans l'application d'origine. Android Auto peut ainsi marquer une conversation comme lue ou y répondre.

Pour créer une Action, commencez par un Intent. L'exemple suivant montre comment créer un Intent "Répondre" :

fun createReplyAction(
        context: Context, appConversation: YourAppConversation): Action {
    val replyIntent: Intent = createReplyIntent(context, appConversation)
    // ...

Encapsulez ensuite cet Intent dans un PendingIntent, qui le préparera en vue d'être utilisé par une application externe. Un PendingIntent verrouille tout accès à l'Intent encapsulé en n'exposant qu'un ensemble sélectionné de méthodes permettant à une application réceptrice de déclencher l'Intent ou d'obtenir le nom de package de l'application d'origine, mais sans jamais autoriser l'application externe à accéder à l'Intent sous-jacent ni aux données qu'il contient.

    // ...
    val replyPendingIntent = PendingIntent.getService(
        context,
        createReplyId(appConversation), // Method explained later.
        replyIntent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
    // ...

Avant de configurer l'Action "Répondre", sachez qu'Android Auto implique trois exigences pour cette Action :

  • L'action sémantique doit être définie sur Action.SEMANTIC_ACTION_REPLY.
  • L'Action doit indiquer qu'elle n'affiche pas d'interface utilisateur lors de son déclenchement.
  • L'Action doit contenir une seule RemoteInput.

L'exemple de code suivant configure une Action "Répondre" qui respecte les exigences ci-dessus :

    // ...
    val replyAction = Action.Builder(R.drawable.reply, "Reply", replyPendingIntent)
        // Provides context to what firing the Action does.
        .setSemanticAction(Action.SEMANTIC_ACTION_REPLY)

        // The action doesn't show any UI, as required by Android Auto.
        .setShowsUserInterface(false)

        // Don't forget the reply RemoteInput. Android Auto will use this to
        // make a system call that will add the response string into
        // the reply intent so it can be extracted by the messaging app.
        .addRemoteInput(createReplyRemoteInput(context))
        .build()

    return replyAction
}

Le traitement de l'action "Marquer comme lu" est semblable, sauf qu'il n'implique pas de RemoteInput. Android Auto comporte donc deux exigences pour l'Action "Marquer comme lu" :

  • L'action sémantique doit être définie sur Action.SEMANTIC_ACTION_MARK_AS_READ.
  • L'action doit indiquer qu'elle n'affiche pas d'interface utilisateur lors de son déclenchement.

L'exemple de code suivant configure une Action "Marquer comme lu" qui respecte ces exigences :

fun createMarkAsReadAction(
        context: Context, appConversation: YourAppConversation): Action {
    val markAsReadIntent = createMarkAsReadIntent(context, appConversation)
    val markAsReadPendingIntent = PendingIntent.getService(
            context,
            createMarkAsReadId(appConversation), // Method explained below.
            markAsReadIntent,
            PendingIntent.FLAG_UPDATE_CURRENT  or PendingIntent.FLAG_IMMUTABLE)
    val markAsReadAction = Action.Builder(
            R.drawable.mark_as_read, "Mark as Read", markAsReadPendingIntent)
        .setSemanticAction(Action.SEMANTIC_ACTION_MARK_AS_READ)
        .setShowsUserInterface(false)
        .build()
    return markAsReadAction
}

Lors de la génération des intents en attente, deux méthodes sont utilisées : createReplyId() et createMarkAsReadId(). Ces méthodes servent de codes de requête pour chaque PendingIntent, qui permettent à Android de contrôler les intents en attente existants. Les méthodes create() doivent renvoyer des ID uniques pour chaque conversation, mais les appels répétés pour la même conversation doivent renvoyer l'ID unique déjà généré.

Prenons l'exemple de deux conversations : A et B. Pour la conversation A, l'ID correspondant à une action "Répondre" est 100 et l'ID correspondant à une action "Marquer comme lu" est 101. Pour la conversation B, l'ID correspondant à une action "Répondre" est 102 et l'ID correspondant à une action "Marquer comme lu" est 103. Si la conversation A est mise à jour, les ID correspondant à une action "Répondre" et à une action "Marquer comme lu" restent 100 et 101. Pour en savoir plus, consultez PendingIntent.FLAG_UPDATE_CURRENT.

Créer un objet MessagingStyle

MessagingStyle transporte les informations de messagerie. Android Auto l'utilise pour lire à voix haute chaque message d'une conversation.

Tout d'abord, l'utilisateur de l'appareil doit être spécifié sous la forme d'un objet Person, comme illustré dans l'exemple suivant :

fun createMessagingStyle(
        context: Context, appConversation: YourAppConversation): MessagingStyle {
    // Method defined by the messaging app.
    val appDeviceUser: YourAppUser = getAppDeviceUser()

    val devicePerson = Person.Builder()
        // The display name (also the name that's read aloud in Android auto).
        .setName(appDeviceUser.name)

        // The icon to show in the notification shade in the system UI (outside
        // of Android Auto).
        .setIcon(appDeviceUser.icon)

        // A unique key in case there are multiple people in this conversation with
        // the same name.
        .setKey(appDeviceUser.id)
        .build()
    // ...

Vous pouvez ensuite construire l'objet MessagingStyle et fournir des informations sur la conversation.

    // ...
    val messagingStyle = MessagingStyle(devicePerson)

    // Sets the conversation title. If the app's target version is lower
    // than P, this will automatically mark the conversation as a group (to
    // maintain backward compatibility). Use `setGroupConversation` after
    // setting the conversation title to explicitly override this behavior. See
    // the documentation for more information.
    messagingStyle.setConversationTitle(appConversation.title)

    // Group conversation means there is more than 1 recipient, so set it as such.
    messagingStyle.setGroupConversation(appConversation.recipients.size > 1)
    // ...

Enfin, ajoutez les messages non lus.

    // ...
    for (appMessage in appConversation.getUnreadMessages()) {
        // The sender is also represented using a Person object.
        val senderPerson = Person.Builder()
            .setName(appMessage.sender.name)
            .setIcon(appMessage.sender.icon)
            .setKey(appMessage.sender.id)
            .build()

        // Adds the message. More complex messages, like images,
        // can be created and added by instantiating the MessagingStyle.Message
        // class directly. See documentation for details.
        messagingStyle.addMessage(
                appMessage.body, appMessage.timeReceived, senderPerson)
    }

    return messagingStyle
}

Empaqueter et envoyer la notification

Après avoir généré les objets Action et MessagingStyle, vous pouvez construire et publier la Notification.

fun notify(context: Context, appConversation: YourAppConversation) {
    // Creates the actions and MessagingStyle.
    val replyAction = createReplyAction(context, appConversation)
    val markAsReadAction = createMarkAsReadAction(context, appConversation)
    val messagingStyle = createMessagingStyle(context, appConversation)

    // Creates the notification.
    val notification = NotificationCompat.Builder(context, channel)
        // A required field for the Android UI.
        .setSmallIcon(R.drawable.notification_icon)

        // Shows in Android Auto as the conversation image.
        .setLargeIcon(appConversation.icon)

        // Adds MessagingStyle.
        .setStyle(messagingStyle)

        // Adds reply action.
        .addAction(replyAction)

        // Makes the mark-as-read action invisible, so it doesn't appear
        // in the Android UI but the app satisfies Android Auto's
        // mark-as-read Action requirement. Both required actions can be made
        // visible or invisible; it is a stylistic choice.
        .addInvisibleAction(markAsReadAction)

        .build()

    // Posts the notification for the user to see.
    val notificationManagerCompat = NotificationManagerCompat.from(context)
    notificationManagerCompat.notify(appConversation.id, notification)
}

Ressources supplémentaires

Signaler un problème de messagerie avec Android Auto

Si vous rencontrez un problème lors du développement de votre application de chat pour Android Auto, vous pouvez le signaler à l'aide de Google Issue Tracker. Veillez à fournir toutes les informations requises dans le modèle dédié.

Signaler un problème

Avant de signaler un nouveau problème, vérifiez s'il figure déjà dans la liste des problèmes. Pour vous abonner et voter pour des problèmes, cliquez sur l'étoile correspondant à un problème dans l'outil Issue Tracker. Pour en savoir plus, consultez S'abonner à un problème.