Rozszerzanie powiadomień o wiadomościach na Androidzie Auto

Aplikacje obsługujące wiadomości mogą rozszerzać powiadomienia o wiadomościach, aby Android Auto mógł z nich korzystać, gdy jest uruchomiony. Te powiadomienia są wyświetlane w Auto i umożliwiają użytkownikom odczytywanie wiadomości oraz odpowiadanie na nie w spójnym interfejsie, który nie rozprasza uwagi. Gdy używasz interfejsu MessagingStyle API, otrzymujesz zoptymalizowane powiadomienia o wiadomościach na wszystkich urządzeniach z Androidem, w tym na Androidzie Auto. Optymalizacje obejmują interfejs użytkownika dostosowany do powiadomień o wiadomościach, ulepszone animacje i obsługę obrazów w tekście.

Z tego przewodnika dowiesz się, jak rozszerzyć aplikację, która wyświetla użytkownikowi wiadomości i odbiera jego odpowiedzi (np. aplikację do czatu), aby przekazywać wyświetlanie wiadomości i odbieranie odpowiedzi na urządzenie Auto. Dzięki tej integracji użytkownicy mogą wyświetlać historię wiadomości tylko z powiadomień otrzymanych podczas aktywnej sesji Androida Auto. Aby wyświetlać wiadomości sprzed rozpoczęcia aktywnej sesji Androida Auto, możesz utworzyć szablonową funkcję wysyłania wiadomości.

Wskazówki dotyczące projektowania znajdziesz w artykule Aplikacje do przesyłania wiadomości w witrynie Design for Driving.

Rozpocznij

Aby udostępniać usługę przesyłania wiadomości na urządzeniach z Androidem Auto, aplikacja musi zadeklarować w pliku manifestu obsługę Androida Auto i umożliwiać:

  • Tworzenie i wysyłanie obiektówNotificationCompat.MessagingStyle zawierających obiekty odpowiedzi i oznaczenia jako przeczytaneAction.
  • Odpowiadaj na wiadomości i oznaczaj rozmowy jako przeczytane za pomocą Service.

Pojęcia i obiekty

Zanim zaczniesz projektować aplikację, warto dowiedzieć się, jak Android Auto obsługuje wiadomości.

Pojedynczy fragment komunikacji nazywa się wiadomością i jest reprezentowany przez klasę MessagingStyle.Message. Wiadomość zawiera nadawcę, treść wiadomości i godzinę wysłania.

Komunikacja między użytkownikami jest nazywana rozmową i jest reprezentowana przez obiekt MessagingStyle. Rozmowa, czyli MessagingStyle, zawiera tytuł, wiadomości oraz informację o tym, czy jest to rozmowa grupowa.

Aby powiadomić użytkowników o aktualizacjach w wątku, np. o nowej wiadomości, aplikacje wysyłają do systemu Android Notification. Ten Notification używa obiektu MessagingStyle do wyświetlania w panelu powiadomień interfejsu użytkownika związanego z wiadomościami. Platforma Android przekazuje też ten Notification do Androida Auto, a MessagingStyle jest wyodrębniany i używany do wyświetlania powiadomień na wyświetlaczu samochodu.

Android Auto wymaga też, aby aplikacje dodawały Action do Notification, aby umożliwić użytkownikowi szybkie odpowiadanie na wiadomości lub oznaczanie ich jako przeczytanych bezpośrednio z poziomu panelu powiadomień.

Podsumowując, pojedyncza rozmowa jest reprezentowana przez obiekt Notification, który jest stylizowany za pomocą obiektu MessagingStyle. Element MessagingStyle zawiera wszystkie wiadomości w danym wątku w co najmniej 1 obiekcie MessagingStyle.Message. Aby aplikacja była zgodna z Androidem Auto, musi dołączać obiekty odpowiedzi i oznaczania jako przeczytane Action do obiektu Notification.

Przepływ wiadomości

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

  1. Aplikacja otrzyma wiadomość.
  2. Aplikacja generuje powiadomienie MessagingStyle z obiektami odpowiedzi i oznaczania jako przeczytane Action.
  3. Android Auto otrzymuje z systemu Android zdarzenie „nowe powiadomienie” i znajduje MessagingStyle, odpowiedź Action i oznaczanie jako przeczytane Action.
  4. Android Auto generuje i wyświetla powiadomienie w samochodzie.
  5. Jeśli użytkownik kliknie powiadomienie na wyświetlaczu w samochodzie, Android Auto wywoła funkcję oznaczania wiadomości jako przeczytanej Action.
    • Aplikacja musi obsługiwać to zdarzenie oznaczania jako przeczytane w tle.
  6. Jeśli użytkownik odpowie na powiadomienie głosowo, Android Auto umieści transkrypcję jego odpowiedzi w odpowiedzi Action, a następnie ją wyśle.
    • W tle aplikacja musi obsługiwać to zdarzenie odpowiedzi.

Wstępne założenia

Ta strona nie zawiera wskazówek dotyczących tworzenia całej aplikacji do obsługi wiadomości. Poniższy przykładowy kod zawiera niektóre elementy, których aplikacja potrzebuje, zanim zacznie obsługiwać wiadomości w Androidzie 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 aplikacji do przesyłania wiadomości, sprawdzi, czy aplikacja zadeklarowała obsługę Androida Auto. Aby włączyć tę obsługę, dodaj do 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 manifeście odnosi się do innego pliku XML, który musisz utworzyć w tej ścieżce: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml. W automotive_app_desc.xml zadeklaruj funkcje Androida Auto, które obsługuje Twoja aplikacja. Aby na przykład zadeklarować obsługę powiadomień, dodaj ten kod:

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

Jeśli Twoja aplikacja może być domyślną aplikacją do obsługi SMS-ów, dodaj ten element <uses>. Jeśli tego nie zrobisz, do obsługi przychodzących SMS-ów i MMS-ów, gdy Twoja aplikacja jest ustawiona jako domyślny moduł obsługi SMS-ów, będzie używany domyślny moduł obsługi wbudowany w Androida Auto, co może prowadzić do duplikowania powiadomień.

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

Importowanie biblioteki podstawowej AndroidX

Tworzenie powiadomień do użytku na urządzeniach z Androidem Auto wymaga biblioteki podstawowej AndroidX. Zaimportuj bibliotekę do projektu w ten sposób:

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

Groovy

allprojects {
    repositories {
        google()
    }
}

Kotlin

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

Groovy

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

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

Kotlin

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

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

Obsługa działań użytkownika

Aplikacja do przesyłania wiadomości musi mieć możliwość aktualizowania rozmowy za pomocą elementu Action. W przypadku Androida Auto aplikacja musi obsługiwać 2 rodzaje Actionobiektów: odpowiedź i oznaczanie jako przeczytane. Zalecamy obsługę tych wywołań za pomocą IntentService, która umożliwia obsługę potencjalnie kosztownych wywołań w tle, co zwalnia główny wątek aplikacji.

Definiowanie działań intencji

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

Przykładowa aplikacja do obsługi wiadomości w tym przewodniku ma 2 wymagane typy działań: odpowiedź i oznaczanie jako przeczytane, jak pokazano w poniższym przykładowym kodzie.

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, czyli dowolnej struktury danych zdefiniowanej przez aplikację, która identyfikuje rozmowę. Potrzebujesz też klucza wejścia zdalnego, który omówimy szczegółowo w dalszej części tej sekcji. Poniższy 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 też zarejestrować ją w pliku manifestu aplikacji, jak pokazano w tym przykładzie:

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

Generowanie intencji i zarządzanie nimi

Inne aplikacje, w tym Android Auto, nie mogą uzyskać Intent, które wywołuje MessagingService, ponieważ Intent są przekazywane do innych aplikacji za pomocą PendingIntent. Z tego powodu musisz utworzyć obiekt RemoteInput, aby umożliwić innym aplikacjom przekazywanie tekstu odpowiedzi do Twojej aplikacji, 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
}

W klauzuli ACTION_REPLY instrukcji MessagingService wyodrębnij informacje, które mają się znaleźć w odpowiedzi Intent, jak pokazano 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)
}

Oznaczanie wiadomości jako przeczytanych Intent działa podobnie. Nie wymaga jednak 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 ACTION_MARK_AS_READ w ramach MessagingService nie wymaga dodatkowej logiki, jak 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łania w konwersacji następnym krokiem jest wygenerowanie powiadomień zgodnych z Androidem Auto.

Tworzenie działań

Obiekty Action można przekazywać do innych aplikacji za pomocą Notification, aby wywoływać metody w oryginalnej aplikacji. W ten sposób Android Auto może oznaczać rozmowę jako przeczytaną lub na nią odpowiadać.

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

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

Następnie umieść ten kod Intent w tagu PendingIntent, aby przygotować go do użycia w aplikacji zewnętrznej. PendingIntent blokuje cały dostęp do opakowanej Intent, udostępniając tylko wybrane metody, które umożliwiają aplikacji odbierającej wywołanie Intent lub uzyskanie nazwy pakietu aplikacji wysyłającej. Aplikacja zewnętrzna nigdy nie ma dostępu do bazowego Intent ani do danych w nim zawartych.

    // ...
    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 mieć wartość Action.SEMANTIC_ACTION_REPLY.
  • Action musi wskazywać, że po uruchomieniu nie będzie wyświetlać żadnego interfejsu.
  • Element Action musi zawierać pojedynczy element RemoteInput.

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

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

Obsługa działania oznaczania jako przeczytane jest podobna, z wyjątkiem tego, że nie ma RemoteInput. Dlatego Android Auto ma 2 wymagania dotyczące funkcji oznaczania wiadomości jako przeczytanych Action:

  • Działanie semantyczne jest ustawione na Action.SEMANTIC_ACTION_MARK_AS_READ.
  • Działanie wskazuje, że po jego uruchomieniu nie będzie wyświetlany żaden interfejs.

Ten przykładowy kod konfiguruje funkcję oznaczania jako przeczytane Action, która 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 oczekujących intencji używane są 2 metody:createReplyId()createMarkAsReadId(). Te metody służą jako kody żądań dla każdego PendingIntent, które są używane przez Androida do kontrolowania istniejących oczekujących intencji. Metody create() muszą zwracać unikalne identyfikatory dla każdej rozmowy, ale powtarzane wywołania tej samej rozmowy muszą zwracać już wygenerowany unikalny identyfikator.

Rozważmy przykład z 2 rozmowami – A i B: identyfikator odpowiedzi w rozmowie A to 100, a identyfikator oznaczania jako przeczytane to 101. Identyfikator odpowiedzi w rozmowie B to 102, a identyfikator oznaczenia jako przeczytane to 103. Jeśli wątek A zostanie zaktualizowany, identyfikatory odpowiedzi i oznaczenia jako przeczytane nadal będą wynosić 100 i 101. Więcej informacji znajdziesz w sekcji PendingIntent.FLAG_UPDATE_CURRENT.

Tworzenie obiektu MessagingStyle

MessagingStyle to operator informacji o wiadomościach. Android Auto używa go do odczytywania na głos każdej wiadomości w rozmowie.

Najpierw użytkownik urządzenia musi być określony w formie 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óły rozmowy.

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

Pakowanie i wysyłanie powiadomienia

Po wygenerowaniu obiektów ActionMessagingStyle możesz utworzyć i opublikować obiekt 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 wiadomościami w Androidzie Auto

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

Tworzenie nowego problemu

Zanim zgłosisz nowy problem, sprawdź, czy nie został on już zgłoszony na liście problemów. Aby zasubskrybować problem i na niego zagłosować, kliknij gwiazdkę przy problemie w narzędziu do śledzenia. Więcej informacji znajdziesz w artykule Subskrybowanie problemu.