Usar mensagens para ficar conectado é importante para muitos motoristas. Os apps de chat podem avisar os usuários se é preciso buscar uma criança ou se vão jantar em outro lugar. O framework do Android permite que os apps de mensagens estendam os serviços para quem está dirigindo, usando uma interface de usuário padrão, que permite que os motoristas possam prestar atenção na estrada.
Os apps que oferecem suporte a mensagens podem estender as notificações de mensagens para permitir o uso delas pelo Android Auto quando estiverem em execução. Essas notificações são mostradas no Auto e permitem que os usuários leiam e respondam às mensagens em uma interface consistente e com pouca distração. E quando você usa a API MessagingStyle, recebe notificações de mensagens otimizadas para todos os dispositivos Android, incluindo o Android Auto. As otimizações incluem uma interface especializada para notificações de mensagens, animações aprimoradas e suporte para imagens inline.
Este guia mostra como estender um app que mostra mensagens e recebe as respostas do usuário, como um app de chat, para entregar a mensagem e enviar a resposta com um dispositivo Auto. Para conferir orientações de design relacionadas, consulte Apps de mensagens no site de Design para direção.
Começar
Para oferecer serviço de mensagens a dispositivos Auto, o app precisa declarar suporte para o Android Auto no manifesto e fazer o seguinte:
- Criar e enviar
objetos
NotificationCompat.MessagingStyle
que contenham objetosAction
para responder e marcar a mensagem como lida. - Processar as ações de responder e marcar uma conversa como lida com um
Service
.
Conceitos e objetos
Antes de começar a projetar seu app, é útil entender como o Android Auto processa as mensagens.
Um bloco individual de comunicação é chamado de mensagem e é representado
pela classe MessagingStyle.Message
. Uma mensagem tem um remetente, o conteúdo
e a hora em que ela foi enviada.
A comunicação entre usuários é chamada de conversa e é representada por um
objeto MessagingStyle
. Uma conversa, ou MessagingStyle
, contém um título,
as mensagens e se a conversa está em um grupo de usuários.
Para notificar os usuários sobre atualizações em uma conversa, como uma nova mensagem, os apps enviam
uma Notification
ao sistema Android.
Essa Notification
usa o objeto MessagingStyle
para mostrar a interface específica da mensagem
na aba de notificações. A plataforma Android também transmite essa Notification
para o Android Auto, e o MessagingStyle
é extraído e usado para mostrar uma
notificação na tela do carro.
O Android Auto também exige que os apps adicionem objetos Action
a uma Notification
para
permitir que o usuário responda rapidamente a uma mensagem ou a marque como lida diretamente na
aba de notificações.
Em resumo, uma única conversa é representada por um objeto Notification
que é estilizado com um objeto MessagingStyle
. O MessagingStyle
contém todas as mensagens dessa conversa em um ou mais
objetos MessagingStyle.Message
. Além disso, para ser compatível com o Android Auto, o app
precisa anexar objetos Action
para responder e marcar a mensagem como lida à Notification
.
Fluxo de mensagens
Esta seção descreve um fluxo de mensagens comum entre seu app e o Android Auto.
- Seu app recebe uma mensagem.
- Seu app gera uma notificação
MessagingStyle
com objetosAction
para responder e marcar a mensagem como lida. - O Android Auto recebe o evento "notificação nova" do sistema Android
e encontra o
MessagingStyle
, aAction
para responder e aAction
para marcar a mensagem como lida. - O Android Auto gera e mostra uma notificação no carro.
- Se o usuário tocar na notificação na tela do carro, o Android Auto
vai acionar a
Action
para marcar a mensagem como lida.- Seu app precisa gerenciar esse evento de marcar como lida em segundo plano.
- Se o usuário responder à notificação por voz, o Android Auto vai incluir
uma transcrição da resposta na
Action
de reposta e a acionará.- Seu app precisa gerenciar esse evento de resposta em segundo plano.
Hipóteses preliminares
Esta página não orienta você sobre como criar um app de mensagens inteiro. O exemplo de código abaixo inclui alguns elementos que seu app precisa ter antes de começar a oferecer suporte ao envio de mensagens pelo 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)
Declarar suporte para Android Auto
Quando o Android Auto recebe uma notificação de um app de mensagens, ele verifica se o app declarou compatibilidade com o Android Auto. Para ativar esse suporte, inclua esta entrada no manifesto do seu app:
<application>
...
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/>
...
</application>
Essa entrada de manifesto se refere a outro arquivo XML que você precisa criar com este
caminho: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml
.
Em automotive_app_desc.xml
, declare os recursos do Android Auto com suporte do
app. Por exemplo, para declarar o suporte a notificações, inclua o
seguinte:
<automotiveApp>
<uses name="notification" />
</automotiveApp>
Caso seu app possa ser definido como o gerenciador de SMS padrão,
inclua o elemento <uses>
abaixo. Se você não fizer isso, um gerenciador padrão
integrado ao Android Auto será usado para processar mensagens SMS/MMS recebidas
quando seu app estiver configurado como o gerenciador padrão, o que pode levar a notificações
duplicadas.
<automotiveApp>
...
<uses name="sms" />
</automotiveApp>
Importar a biblioteca principal do AndroidX
A criação de notificações para uso com dispositivos Auto exige a biblioteca principal do AndroidX. Importe a biblioteca para o projeto desta forma:
- No arquivo
build.gradle
de nível superior, inclua uma dependência no repositório Maven do Google, conforme mostrado no exemplo abaixo:
Groovy
allprojects { repositories { google() } }
Kotlin
allprojects { repositories { google() } }
- No arquivo
build.gradle
do módulo do seu app, inclua a dependência da biblioteca AndroidX Core, conforme mostrado no exemplo abaixo:
Groovy
dependencies { // If your app is written in Java implementation 'androidx.core:core:1.15.0' // If your app is written in Kotlin implementation 'androidx.core:core-ktx:1.15.0' }
Kotlin
dependencies { // If your app is written in Java implementation("androidx.core:core:1.15.0") // If your app is written in Kotlin implementation("androidx.core:core-ktx:1.15.0") }
Processar ações do usuário
Seu app de mensagens precisa de uma maneira de processar atualizações em uma conversa por uma
Action
. Para o Android Auto, existem dois tipos de objetos Action
que o app
precisa processar: responder e marcar como lida. Recomendamos processá-los usando
uma IntentService
, que
oferece a flexibilidade de processar chamadas potencialmente
caras em segundo plano, liberando a linha de execução principal.
Definir ações de intents
As ações Intent
são strings simples que identificam para que serve a Intent
.
Como um único serviço pode processar vários tipos de intents, é mais fácil
definir várias strings de ação em vez de definir vários
componentes IntentService
.
O app de mensagens de exemplo deste guia tem os dois tipos obrigatórios de ação: responder e marcar a mensagem como lida, conforme mostrado no exemplo de código abaixo.
private const val ACTION_REPLY = "com.example.REPLY"
private const val ACTION_MARK_AS_READ = "com.example.MARK_AS_READ"
Criar o serviço
Para criar um serviço que gerencie esses objetos Action
, você precisa do ID da conversa,
que é uma estrutura de dados arbitrária definida pelo seu app que identifica
a conversa. Você também precisa de uma chave de entrada remota, discutida
em detalhes mais adiante nesta seção. O exemplo de código abaixo cria um serviço
para processar as ações necessárias:
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()
}
}
}
Para associar esse serviço ao seu app, você também precisa registrá-lo no manifesto do app, como mostrado neste exemplo:
<application>
<service android:name="com.example.MessagingService" />
...
</application>
Gerar e processar intents
Não é possível para outros apps, incluindo o Android Auto, acessar a Intent
que aciona o MessagingService
, porque as Intent
s são transmitidas para outros
apps usando uma PendingIntent
. Devido a
essa limitação, é necessário criar um objeto RemoteInput
para permitir que outros apps forneçam o texto de resposta ao app, conforme mostrado
neste exemplo:
/**
* 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
}
Na cláusula de chave ACTION_REPLY
dentro do MessagingService
,
extraia as informações da Intent
"responder", conforme mostrado no
exemplo abaixo:
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)
}
Gerencie a Intent
"marcar mensagem como lida" de maneira semelhante. No entanto, ela não
exige uma RemoteInput
, conforme mostrado neste exemplo:
/** 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
}
A cláusula de chave ACTION_MARK_AS_READ
dentro do MessagingService
não exige mais lógica, como mostrado neste exemplo:
// Marking as read has no other logic.
ACTION_MARK_AS_READ -> conversation.markAsRead()
Notificar usuários de mensagens
Depois que o processamento da ação de conversa for concluído, a próxima etapa será gerar notificações compatíveis com o Android Auto.
Criar ações
Objetos Action
podem ser transmitidos para outros apps usando uma Notification
para
acionar métodos no app original. É assim que o Android Auto pode marcar uma
conversa como lida ou responder a ela.
Para criar uma Action
, comece com uma Intent
. O exemplo abaixo mostra
como criar uma Intent
de "resposta":
fun createReplyAction(
context: Context, appConversation: YourAppConversation): Action {
val replyIntent: Intent = createReplyIntent(context, appConversation)
// ...
Em seguida, envolvemos essa Intent
em uma PendingIntent
que a prepara para o uso
externo do app. Uma PendingIntent
bloqueia todo o acesso à Intent
encapsulada
expondo apenas um conjunto selecionado de métodos que permitem que o app receptor dispare a
Intent
ou receba o nome do pacote do app de origem. O app externo
nunca pode acessar a Intent
ou os dados dela.
// ...
val replyPendingIntent = PendingIntent.getService(
context,
createReplyId(appConversation), // Method explained later.
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
// ...
Antes de configurar a Action
de resposta, o Android Auto tem três
requisitos para essa Action
:
- A ação semântica precisa ser configurada como
Action.SEMANTIC_ACTION_REPLY
. - A
Action
precisa indicar que não mostrará nenhuma interface do usuário quando acionada. - A
Action
precisa conter uma únicaRemoteInput
.
O exemplo de código abaixo configura uma Action
de resposta que atende aos
requisitos listados acima:
// ...
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
}
O processamento da ação "marcar como lida" é semelhante, exceto pelo fato de não haver uma RemoteInput
.
O Android Auto tem dois requisitos para a Action
de "marcar como lida":
- A ação semântica precisa ser definida como
Action.SEMANTIC_ACTION_MARK_AS_READ
. - A ação indica que não mostrará nenhuma interface do usuário quando acionada.
O exemplo de código abaixo configura uma Action
"marcar como lida" que atende a esses
requisitos:
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
}
Ao gerar as intents pendentes, dois métodos são usados:
createReplyId()
e createMarkAsReadId()
. Esses métodos servem como
códigos de solicitação para cada PendingIntent
, que são usados pelo Android para controlar
as intents pendentes já existentes. Os métodos create()
precisam
retornar IDs exclusivos para cada conversa, mas chamadas repetidas para a mesma
conversa precisam retornar o ID exclusivo já gerado.
Considere um exemplo com duas conversas: A e B: o ID de resposta da conversa A é 100,
e o ID de marcar como lida é 101. O ID de resposta da conversa B é
102, e o ID de marcar como lida é 103. Se a conversa A for atualizada, os
IDs de resposta e de marcar como lida ainda serão 100 e 101. Para saber mais, consulte
PendingIntent.FLAG_UPDATE_CURRENT
.
Criar um MessagingStyle
O MessagingStyle
contém as informações das mensagens e é usado pelo Android
Auto para ler mensagens de uma conversa em voz alta.
Primeiro, o usuário do dispositivo precisa ser especificado na forma de um objeto
Person
, conforme mostrado
neste exemplo:
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()
// ...
Em seguida, construa o objeto MessagingStyle
e forneça alguns detalhes
sobre a conversa.
// ...
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)
// ...
Por fim, adicione as mensagens não lidas.
// ...
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
}
Empacotar e enviar a notificação
Depois de gerar os objetos Action
e MessagingStyle
, você pode
construir e postar a 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)
}
Outros recursos
Informar um problema com mensagens no Android Auto
Se você encontrar um problema ao desenvolver seu app de mensagens para o Android Auto, informe-o usando o Google Issue Tracker. Preencha todas as informações solicitadas no modelo de problema.
Antes de informar um novo problema, verifique se ele já foi comunicado na lista. Inscreva-se e vote nos problemas clicando na estrela de um deles na lista de problemas. Para ver mais informações, consulte Como se inscrever em um problema.