Messaging-Apps für Android Auto entwickeln

Für viele Autofahrer ist es wichtig, durch Nachrichten in Verbindung zu bleiben. Chat-Apps können Nutzer darüber informieren, ob ein Kind abgeholt werden muss oder der Ort für das Abendessen geändert wurde. Mit dem Android-Framework können Messaging-Apps ihre Dienste auf das Fahrerlebnis erweitern. Dazu wird eine standardmäßige Benutzeroberfläche verwendet, mit der Fahrer den Straßenverkehr im Auge behalten können.

Apps, die Messaging unterstützen, können ihre Benachrichtigungen so erweitern, dass Android Auto sie nutzen kann, wenn Auto ausgeführt wird. Diese Benachrichtigungen werden in der automatischen Version angezeigt und bieten Nutzern die Möglichkeit, Nachrichten auf einer einheitlichen Oberfläche mit geringer Ablenkung zu lesen und zu beantworten. Wenn Sie die MessagingStyle API verwenden, erhalten Sie optimierte Nachrichtenbenachrichtigungen für alle Android-Geräte, einschließlich Android Auto. Zu den Optimierungen gehören eine Benutzeroberfläche speziell für Nachrichtenbenachrichtigungen, verbesserte Animationen und Unterstützung von Inline-Bildern.

In diesem Leitfaden wird beschrieben, wie du eine App, die Nachrichten für den Nutzer anzeigt und die Antworten des Nutzers empfängt (z. B. eine Chat-App), so erweitert, dass Nachrichten angezeigt und Empfangsbestätigungen an ein Auto-Gerät gesendet werden. Entsprechende Anleitungen finden Sie unter Messaging-Apps auf der Website „Design for Driving“.

Erste Schritte

Damit du den Nachrichtendienst für Android Auto-Geräte nutzen kannst, muss deine App im Manifest die Unterstützung für Android Auto deklarieren und folgende Aktionen ausführen:

  • Erstellen und senden Sie NotificationCompat.MessagingStyle-Objekte, die Antwort- und „Als gelesen markieren“-Action-Objekte enthalten.
  • Mit Service kannst du auf Unterhaltungen antworten und Unterhaltungen als gelesen markieren.

Konzepte und Objekte

Bevor du mit dem Entwerfen deiner App beginnst, solltest du wissen, wie Android Auto Nachrichten verarbeitet.

Ein einzelner Kommunikationsblock wird als Nachricht bezeichnet und durch die Klasse MessagingStyle.Message dargestellt. Eine Nachricht enthält einen Absender, den Nachrichteninhalt und die Uhrzeit, zu der die Nachricht gesendet wurde.

Die Kommunikation zwischen Nutzern wird als Unterhaltung bezeichnet und durch ein MessagingStyle-Objekt dargestellt. Eine Unterhaltung oder MessagingStyle enthält einen Titel, die Nachrichten und Informationen dazu, ob die Unterhaltung zu einer Gruppe von Nutzern gehört.

Apps posten Notification an das Android-System, um Nutzer über Aktualisierungen einer Unterhaltung zu informieren, z. B. über eine neue Nachricht. Notification verwendet das MessagingStyle-Objekt, um eine nachrichtenspezifische UI in der Benachrichtigungsleiste anzuzeigen. Die Android-Plattform übergibt diese Notification auch an Android Auto, die MessagingStyle wird extrahiert und verwendet, um eine Benachrichtigung über das Display des Autos zu posten.

In Android Auto müssen Apps außerdem Action-Objekte zu einer Notification hinzufügen, damit der Nutzer schnell auf eine Nachricht antworten oder sie direkt über die Benachrichtigungsleiste als gelesen markieren kann.

Eine einzelne Unterhaltung wird also durch ein Notification-Objekt mit einem MessagingStyle-Objekt dargestellt. MessagingStyle enthält alle Nachrichten in dieser Unterhaltung in einem oder mehreren MessagingStyle.Message-Objekten. Um Android Auto-konform zu sein, muss eine App außerdem Antwort- und „Als gelesen markieren“-Action-Objekte an das Notification anhängen.

Kommunikationsablauf

In diesem Abschnitt wird ein typischer Nachrichtenfluss zwischen Ihrer App und Android Auto beschrieben.

  1. Ihre App empfängt eine Nachricht.
  2. Ihre App generiert eine MessagingStyle-Benachrichtigung mit Antwort- und Action-Objekten als gelesen markieren.
  3. Android Auto empfängt das Ereignis „Neue Benachrichtigung“ vom Android-System und findet MessagingStyle, antwortet mit Action und als gelesen markieren (Action).
  4. Android Auto generiert eine Benachrichtigung im Auto und zeigt sie an.
  5. Wenn der Nutzer auf die Benachrichtigung auf dem Display des Autos tippt, löst Android Auto das „Als gelesen markieren“ Action aus.
    • Ihre App muss dieses „Als gelesen markieren“-Ereignis im Hintergrund verarbeiten.
  6. Wenn der Nutzer per Spracheingabe auf die Benachrichtigung antwortet, fügt Android Auto ein Transkript der Nutzerantwort in das Antwort-Action ein und löst es dann aus.
    • Ihre App muss dieses Antwortereignis im Hintergrund verarbeiten.

Vorläufige Annahmen

Diese Seite führt dich nicht durch die Erstellung einer vollständigen Messaging-App. Das folgende Codebeispiel enthält einige der Dinge, die deine App benötigt, bevor du die Nachrichtenfunktion mit Android Auto unterstützen kannst:

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)

Android Auto-Unterstützung deklarieren

Wenn Android Auto eine Benachrichtigung von einer Messaging-App erhält, wird geprüft, ob die App Unterstützung für Android Auto erklärt hat. Füge den folgenden Eintrag in das Manifest deiner App ein, um diese Unterstützung zu aktivieren:

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

Dieser Manifesteintrag verweist auf eine andere XML-Datei, die Sie mit dem folgenden Pfad erstellen müssen: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml. Gib in automotive_app_desc.xml die Android Auto-Funktionen an, die deine App unterstützt. So können Sie beispielsweise die Unterstützung für Benachrichtigungen deklarieren:

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

Wenn Ihre App als Standard-SMS-Handler festgelegt werden kann, fügen Sie das folgende <uses>-Element hinzu. Andernfalls wird ein in Android Auto integrierter Standard-Handler zur Verarbeitung eingehender SMS/MMS verwendet, wenn Ihre App als Standard-SMS-Handler festgelegt ist. Dies kann zu doppelten Benachrichtigungen führen.

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

AndroidX-Kernbibliothek importieren

Zum Erstellen von Benachrichtigungen zur Verwendung mit Auto-Geräten ist die AndroidX-Kernbibliothek erforderlich. Importieren Sie die Bibliothek wie folgt in Ihr Projekt:

  1. Fügen Sie in der Datei build.gradle auf oberster Ebene eine Abhängigkeit vom Maven-Repository von Google ein, wie im folgenden Beispiel gezeigt:

Groovig

allprojects {
    repositories {
        google()
    }
}

Kotlin

allprojects {
    repositories {
        google()
    }
}
  1. Füge in die Datei build.gradle deines App-Moduls die AndroidX Core-Bibliotheksabhängigkeit ein, wie im folgenden Beispiel gezeigt:

Groovig

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

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

Kotlin

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

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

Nutzeraktionen verarbeiten

Deine Messaging-App benötigt eine Möglichkeit, eine Unterhaltung über Action zu aktualisieren. Für Android Auto gibt es zwei Arten von Action-Objekten, die deine App verarbeiten muss: Antworten und Als gelesen markieren. Wir empfehlen, sie mit einem IntentService zu verarbeiten. Dadurch können potenziell teure Aufrufe im Hintergrund ausgeführt werden und der Hauptthread der Anwendung wird frei.

Intent-Aktionen definieren

Intent-Aktionen sind einfache Strings, die angeben, wofür die Intent bestimmt ist. Da ein einzelner Dienst mehrere Intent-Typen verarbeiten kann, ist es einfacher, mehrere Aktionsstrings zu definieren, anstatt mehrere IntentService-Komponenten zu definieren.

Die beispielhafte Messaging-App dieses Leitfadens enthält die zwei erforderlichen Aktionstypen: „Antworten“ und „Als gelesen markieren“, wie im folgenden Codebeispiel gezeigt.

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

Dienst erstellen

Zum Erstellen eines Dienstes, der diese Action-Objekte verarbeitet, benötigen Sie die Unterhaltungs-ID. Dabei handelt es sich um eine beliebige Datenstruktur, die von Ihrer Anwendung definiert wird und die Unterhaltung identifiziert. Sie benötigen außerdem einen Remote-Eingabeschlüssel, der weiter unten in diesem Abschnitt ausführlich erläutert wird. Im folgenden Codebeispiel wird ein Dienst erstellt, um die erforderlichen Aktionen zu verarbeiten:

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

Um diesen Dienst mit Ihrer Anwendung zu verknüpfen, müssen Sie den Dienst auch im Manifest Ihrer Anwendung registrieren, wie im folgenden Beispiel gezeigt:

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

Intents generieren und verarbeiten

Andere Apps, einschließlich Android Auto, haben keine Möglichkeit, das Intent abzurufen, das MessagingService auslöst, weil Intents über eine PendingIntent an andere Apps übergeben werden. Aufgrund dieser Einschränkung müssen Sie ein RemoteInput-Objekt erstellen, damit andere Apps den Antworttext an Ihre App zurückgeben können, wie im folgenden Beispiel gezeigt:

/**
 * 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
}

Extrahieren Sie wie im folgenden Beispiel in der ACTION_REPLY-Schalterklausel innerhalb von MessagingService die Informationen, die in die Intent-Antwort eingegeben werden:

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)
}

Intent wird auf ähnliche Weise verarbeitet. RemoteInput ist jedoch nicht erforderlich, wie im folgenden Beispiel gezeigt:

/** 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
}

Die Wechselklausel ACTION_MARK_AS_READ in der MessagingService erfordert keine weitere Logik, wie im folgenden Beispiel gezeigt:

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

Nutzer über Nachrichten informieren

Sobald die Verarbeitung der Unterhaltungsaktionen abgeschlossen ist, müssen Sie als Nächstes Benachrichtigungen generieren, die mit Android Auto kompatibel sind.

Aktionen erstellen

Action-Objekte können mithilfe eines Notification an andere Apps übergeben werden, um Methoden in der ursprünglichen App auszulösen. Auf diese Weise kann Android Auto eine Unterhaltung als gelesen oder beantworten.

Wenn Sie ein Action-Objekt erstellen möchten, beginnen Sie mit einem Intent. Das folgende Beispiel zeigt, wie du eine Antwort-Intent erstellst:

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

Verpacken Sie dann diese Intent in eine PendingIntent, die sie für die externe Anwendungsnutzung vorbereitet. Ein PendingIntent sperrt den gesamten Zugriff auf das umschlossene Intent, indem nur ausgewählte Methoden verfügbar gemacht werden, mit denen die empfangende App das Intent auslösen oder den Paketnamen der ursprünglichen App abrufen kann. Die externe Anwendung kann niemals auf das zugrunde liegende Intent oder die darin enthaltenen Daten zugreifen.

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

Bevor du die Antwort-Action einrichtest, beachte, dass Android Auto drei Anforderungen für die Antwort-Action hat:

  • Die semantische Aktion muss auf Action.SEMANTIC_ACTION_REPLY festgelegt sein.
  • Die Action muss angeben, dass beim Auslösen keine Benutzeroberfläche angezeigt wird.
  • Action muss ein einzelnes RemoteInput-Element enthalten.

Im folgenden Codebeispiel wird eine Antwort-Action eingerichtet, die die oben aufgeführten Anforderungen erfüllt:

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

Die Aktion „Als gelesen markieren“ wird ähnlich verarbeitet, es wird jedoch kein RemoteInput-Objekt verwendet. Android Auto hat daher zwei Anforderungen für das „Mark-as-read“-Action:

  • Die semantische Aktion ist auf Action.SEMANTIC_ACTION_MARK_AS_READ festgelegt.
  • Die Aktion gibt an, dass beim Auslösen keine Benutzeroberfläche angezeigt wird.

Im folgenden Codebeispiel wird eine „Mark-as-Read“-Action eingerichtet, die diese Anforderungen erfüllt:

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
}

Beim Generieren der ausstehenden Intents werden zwei Methoden verwendet: createReplyId() und createMarkAsReadId(). Diese Methoden dienen als Anfragecodes für jeden PendingIntent, die von Android zur Steuerung vorhandener ausstehender Intents verwendet werden. Die create()-Methoden müssen eindeutige IDs für jede Unterhaltung zurückgeben, wiederholte Aufrufe für dieselbe Unterhaltung müssen jedoch die bereits generierte eindeutige ID zurückgeben.

Nehmen wir ein Beispiel mit zwei Unterhaltungen, A und B: Die Antwort-ID von Unterhaltung A lautet 100 und die Mark-as-Read-ID 101. Die Antwort-ID von Konversation B lautet 102 und die Mark-as-Read-ID 103. Wenn Konversation A aktualisiert wird, sind die IDs für Antwort und Mark-as-Read weiterhin 100 und 101. Weitere Informationen findest du unter PendingIntent.FLAG_UPDATE_CURRENT.

MessagingStyle erstellen

MessagingStyle ist der Träger der Nachrichteninformationen und wird von Android Auto verwendet, um jede Nachricht in einer Unterhaltung vorzulesen.

Zuerst muss der Nutzer des Geräts in Form eines Person-Objekts angegeben werden, wie im folgenden Beispiel gezeigt:

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()
    // ...

Sie können dann das MessagingStyle-Objekt erstellen und einige Details zur Unterhaltung angeben.

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

Fügen Sie abschließend die ungelesenen Nachrichten hinzu.

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

Benachrichtigung verpacken und per Push übertragen

Nachdem Sie die Objekte Action und MessagingStyle generiert haben, können Sie die Notification erstellen und hochladen.

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)
}

Weitere Informationen

Problem mit Android Auto Messaging melden

Wenn bei der Entwicklung der Messaging-App für Android Auto ein Problem auftritt, kannst du es über den Google Issue Tracker melden. Geben Sie in der Problemvorlage alle erforderlichen Informationen an.

Neues Problem erstellen

Bevor Sie ein neues Problem melden, prüfen Sie, ob es bereits in der Problemliste aufgeführt ist. Sie können Themen abonnieren und für sie abstimmen, indem Sie im Tracker auf den Stern für ein Problem klicken. Weitere Informationen finden Sie unter Ausgaben abonnieren.