Tworzenie aplikacji do obsługi wiadomości na Androida Auto

Utrzymywanie kontaktu za pomocą wiadomości jest ważne dla wielu kierowców. Aplikacje do obsługi czatu mogą informować użytkowników o konieczności odebrania dziecka lub zmianie miejsca, w którym można zjeść posiłek. Platforma Androida pozwala aplikacjom do obsługi wiadomości wzbogacić swoje usługi o funkcję jazdy przy użyciu standardowego interfejsu, który pozwala kierowcom skupić się na drodze.

Aplikacje obsługujące wiadomości mogą rozszerzyć powiadomienia o wiadomościach, aby umożliwić Androidowi Auto ich korzystanie, gdy działa Auto. Te powiadomienia wyświetlają się w trybie automatycznym i pozwalają użytkownikom czytać wiadomości i odpowiadać na nie w spójnym interfejsie, który nie rozprasza uwagi. Jeśli korzystasz z interfejsu MessagingStyle API, otrzymujesz zoptymalizowane powiadomienia o wiadomościach na wszystkich urządzeniach z Androidem, w tym na Androidzie Auto. Optymalizacje obejmują interfejs specjalny w przypadku powiadomień o wiadomościach, ulepszone animacje i obsługę obrazów w treści.

Z tego przewodnika dowiesz się, jak rozszerzyć aplikację, która wyświetla wiadomości i otrzymuje odpowiedzi użytkownika (np. aplikację do obsługi czatu), aby wyświetlać wiadomości i odpowiadać na potwierdzenia na urządzeniu Auto. Powiązane wskazówki dotyczące projektowania znajdziesz w artykule o aplikacjach do obsługi wiadomości w witrynie Design for Driving.

Rozpocznij

Aby móc udostępniać usługę przesyłania wiadomości na urządzeniach Auto, Twoja aplikacja musi zadeklarować w pliku manifestu obsługę Androida Auto i mieć możliwość:

  • Twórz i wysyłaj obiekty NotificationCompat.MessagingStyle, które zawierają obiekty odpowiedzi i oznaczenia jako przeczytane Action.
  • Aby odpowiedzieć na wiadomość i oznaczyć wątek jako przeczytany, użyj funkcji Service.

Pojęcia i obiekty

Zanim zaczniesz projektować aplikację, dowiedz się, jak Android Auto obsługuje przesyłanie wiadomości.

Każdy fragment komunikacji jest nazywany wiadomością i jest reprezentowany przez klasę MessagingStyle.Message. Zawiera ona nadawcę, treść i godzinę wysłania.

Komunikacja między użytkownikami jest nazywana rozmową i jest reprezentowana przez obiekt MessagingStyle. Wątek (MessagingStyle) zawiera tytuł, wiadomości i informacje o tym, czy wątek należy do grupy użytkowników.

Aby powiadamiać użytkowników o aktualizacjach w wątku (np. o nowej wiadomości), aplikacje publikują Notification w systemie Android. Ten interfejs Notification używa obiektu MessagingStyle do wyświetlania interfejsu użytkownika w obszarze powiadomień. Platforma Androida przekazuje też ten Notification do Androida Auto, a MessagingStyle jest wyodrębniany i używany do wysłania powiadomienia na wyświetlaczu w samochodzie.

Android Auto wymaga też, aby aplikacje dodały do obiektu Notification obiekty Action, aby umożliwić użytkownikowi szybkie odpowiadanie na wiadomość lub oznaczenie jej jako przeczytanej bezpośrednio w obszarze powiadomień.

Podsumowując, pojedynczy wątek jest reprezentowany przez obiekt Notification ze stylem obiektu MessagingStyle. MessagingStyle zawiera wszystkie wiadomości z danego wątku w co najmniej jednym obiekcie MessagingStyle.Message. Aby aplikacja była zgodna z Androidem Auto, musi dołączać odpowiedzi i oznaczać jako przeczytane obiekty Action do Notification.

Przepływ wiadomości

W tej sekcji opisujemy typowy przepływ wiadomości między aplikacją a Androidem Auto.

  1. Aplikacja otrzyma komunikat.
  2. Aplikacja generuje powiadomienie MessagingStyle z obiektami odpowiedzi i oznaczenia jako przeczytane (Action).
  3. Android Auto odbiera zdarzenie „nowe powiadomienie” z systemu Android i znajduje MessagingStyle, odpowiedź Action i oznaczenie jako przeczytane Action.
  4. Android Auto generuje i wyświetla powiadomienie w samochodzie.
  5. Gdy użytkownik kliknie powiadomienie na wyświetlaczu w samochodzie, Android Auto wywoła oznaczenie jako przeczytane (Action).
    • Aplikacja musi obsługiwać to zdarzenie oznaczenia jako przeczytanego w tle.
  6. Jeśli użytkownik odpowie na powiadomienie głosowo, Android Auto umieszcza transkrypcję odpowiedzi użytkownika w odpowiedzi Action i ją aktywuje.
    • Aplikacja musi obsługiwać to zdarzenie odpowiedzi w tle.

Założenia wstępne

Ta strona nie zawiera wskazówek dotyczących tworzenia całej aplikacji do obsługi wiadomości. Ten przykładowy kod zawiera informacje o tym, czego potrzebuje Twoja aplikacja, zanim zaczniesz obsługiwać przesyłanie wiadomości z Androidem 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)

Deklarowanie obsługi Androida Auto

Gdy Android Auto otrzyma powiadomienie z komunikatora, sprawdza, czy aplikacja zadeklarowała obsługę Androida Auto. Aby włączyć tę obsługę, umieść w pliku manifestu aplikacji ten wpis:

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

Ten wpis w pliku manifestu odnosi się do innego pliku XML, który musisz utworzyć przy użyciu tej ścieżki: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml. W polu automotive_app_desc.xml zadeklaruj funkcje Androida Auto obsługiwane przez Twoją aplikację. Aby na przykład zadeklarować obsługę powiadomień, podaj ten element:

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

Jeśli aplikację można ustawić jako domyślną aplikację do obsługi SMS-ów, pamiętaj, by uwzględnić poniższy element <uses>. Jeśli tego nie zrobisz, przychodzące SMS-y i MMS-y będą obsługiwać w Androidzie Auto za pomocą domyślnej aplikacji do obsługi SMS-ów, co może skutkować duplikowaniem powiadomień.

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

Importowanie biblioteki podstawowej AndroidX

Tworzenie powiadomień na potrzeby urządzeń Auto wymaga głównej biblioteki AndroidX. Zaimportuj bibliotekę do projektu w ten sposób:

  1. W pliku build.gradle najwyższego poziomu dodaj zależność z repozytorium Maven Google, jak w tym przykładzie:

Odlotowy

allprojects {
    repositories {
        google()
    }
}

Kotlin

allprojects {
    repositories {
        google()
    }
}
  1. W pliku build.gradle modułu aplikacji umieść zależność biblioteki AndroidX Core, jak w tym przykładzie:

Odlotowy

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

Wykonaj działania użytkownika

Aplikacja do obsługi wiadomości musi obsługiwać aktualizowanie rozmowy za pomocą Action. W przypadku Androida Auto istnieją 2 typy obiektów Action, które musi obsługiwać aplikacja: odpowiedź i oznaczenie jako przeczytane. Zalecamy obsługę takich wywołań za pomocą IntentService, który zapewnia elastyczność obsługi potencjalnie kosztownych wywołań w tle, uwalniając główny wątek aplikacji.

Definiowanie działań intencji

Działania Intent to proste ciągi znaków, które określają, do czego służy Intent. Ponieważ pojedyncza usługa może obsługiwać wiele typów intencji, łatwiej jest zdefiniować wiele ciągów działań zamiast definiować wiele komponentów IntentService.

Przykładowa aplikacja do obsługi wiadomości w tym przewodniku ma 2 wymagane typy działań: odpowiedź i oznaczenie jako przeczytane – tak jak w tym przykładzie kodu.

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

Tworzenie usługi

Aby utworzyć usługę, która obsługuje te obiekty Action, potrzebujesz identyfikatora rozmowy. Jest to dowolna struktura danych zdefiniowana przez aplikację, która identyfikuje rozmowę. Potrzebujesz też klawisza zdalnego wprowadzania danych, co zostało szczegółowo omówione w tej sekcji. Ten przykładowy kod tworzy usługę do obsługi wymaganych działań:

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

Aby powiązać tę usługę z aplikacją, musisz ją też zarejestrować w pliku manifestu aplikacji, jak pokazano w tym przykładzie:

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

Generowanie i obsługa intencji

Inne aplikacje, w tym Android Auto, nie mogą uzyskać parametru Intent, który uruchamia wywołanie MessagingService, ponieważ sygnały Intent są przekazywane innym aplikacjom przez PendingIntent. Z powodu tego ograniczenia musisz utworzyć obiekt RemoteInput, aby inne aplikacje mogły wysyłać do Twojej aplikacji treść odpowiedzi, jak pokazano w tym przykładzie:

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

Z klauzuli przełączania ACTION_REPLY w obrębie MessagingService wyodrębnij informacje, które znajdują się w odpowiedzi Intent, jak w tym przykładzie:

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

Oznaczenie jako przeczytane (Intent) działa w podobny sposób. Nie jest jednak wymagany RemoteInput, jak pokazano w tym przykładzie:

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

Klauzula przełączania ACTION_MARK_AS_READ w obrębie MessagingService nie wymaga dodatkowej logiki, jak widać w tym przykładzie:

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

Powiadamianie użytkowników o wiadomościach

Po zakończeniu obsługi działań w ramach rozmowy następnym krokiem jest wygenerowanie powiadomień zgodnych z Androidem Auto.

Utwórz działania

Obiekty Action można przekazywać do innych aplikacji za pomocą Notification, aby aktywować metody w pierwotnej aplikacji. W ten sposób Android Auto może oznaczyć rozmowę jako przeczytaną lub na nią odpowiedzieć.

Aby utworzyć Action, rozpocznij od Intent. Ten przykład pokazuje, jak utworzyć Intent „odpowiedź”:

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

Następnie zapakuj obiekt Intent w urządzenie PendingIntent, aby przygotować go do użycia w aplikacjach zewnętrznych. PendingIntent blokuje cały dostęp do opakowanych elementów Intent, ujawniając tylko wybrany zestaw metod, które pozwalają aplikacji docelowej na uruchomienie polecenia Intent lub pobranie nazwy pakietu aplikacji źródłowej. Aplikacja zewnętrzna nie będzie miała nigdy dostępu do bazowego elementu Intent ani zawartych w nim danych.

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

Zanim skonfigurujesz odpowiedź Action, pamiętaj, że Android Auto ma 3 wymagania dotyczące odpowiedzi Action:

  • Działanie semantyczne musi być ustawione na Action.SEMANTIC_ACTION_REPLY.
  • Action musi wskazywać, że po uruchomieniu nie będzie wyświetlać żadnego interfejsu.
  • Pole Action musi zawierać jeden RemoteInput.

Ten przykładowy kod konfiguruje odpowiedź Action, która spełnia wymienione powyżej wymagania:

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

Działanie oznaczania jako przeczytane działa podobnie z wyjątkiem działania RemoteInput. Android Auto ma więc 2 wymagania dotyczące oznaczenia jako przeczytanego (Action):

  • Działanie semantyczne jest ustawione na Action.SEMANTIC_ACTION_MARK_AS_READ.
  • To działanie wskazuje, że po uruchomieniu nie wyświetli się żaden interfejs.

Ten przykładowy kod konfiguruje element Action do oznaczenia jako przeczytany, który spełnia te wymagania:

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
}

Podczas generowania intencji oczekujących można używać 2 metod: createReplyId() i createMarkAsReadId(). Te metody służą jako kody żądań dla poszczególnych elementów PendingIntent, które Android używa do kontrolowania istniejących intencji oczekujących. Metody create() muszą zwracać unikalne identyfikatory dla każdej rozmowy, ale powtarzające się wywołania tego samego rozmowy muszą zwracać unikalny identyfikator, który został już wygenerowany.

Przeanalizujmy przykład z 2 wątkami: A i B. Identyfikator odpowiedzi rozmowy A to 100, a identyfikator oznaczenia jako przeczytanej – 101. Identyfikator odpowiedzi na rozmowę B to 102, a identyfikator oznaczenia jako przeczytanej – 103. Jeśli wątek A zostanie zaktualizowany, identyfikatory odpowiedzi i oznaczenia jako przeczytane nadal będą mieć 100 i 101. Więcej informacji: PendingIntent.FLAG_UPDATE_CURRENT.

Tworzenie stylu wiadomości

MessagingStyle to przewoźnik informacji o wiadomościach, a Android Auto używa do odczytywania na głos każdej wiadomości w rozmowie.

Najpierw użytkownika urządzenia należy określić w postaci obiektu Person, jak w tym przykładzie:

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

Następnie możesz utworzyć obiekt MessagingStyle i podać szczegółowe informacje o rozmowie.

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

Na koniec dodaj nieprzeczytane wiadomości.

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

Tworzenie pakietu i wysyłanie powiadomienia push

Po wygenerowaniu obiektów Action i MessagingStyle możesz utworzyć i opublikować 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)
}

Dodatkowe materiały

Zgłaszanie problemów z Androidem Auto Messaging

Jeśli podczas tworzenia aplikacji do obsługi wiadomości na Androida Auto napotkasz problem, możesz go zgłosić, korzystając z narzędzia Google Issue Tracker. Pamiętaj, aby w szablonie problemu podać wszystkie wymagane informacje.

Utwórz nowy numer

Zanim zgłosisz nowy problem, sprawdź, czy nie ma go już na liście problemów. Możesz zasubskrybować temat i głosować na nie, klikając gwiazdkę przy danym problemie w narzędziu do śledzenia. Więcej informacji znajdziesz w artykule Subskrybowanie problemu.