Mantenerse conectado a través de mensajes es importante para muchos conductores. Con las apps de chat, los usuarios pueden saber si deben ir a buscar a su hijo o si cambió el lugar de una cena. El framework de Android permite que las apps de mensajería extiendan sus servicios a la experiencia de manejo con una interfaz de usuario estándar que les permite a los conductores mantener sus ojos en la ruta.
Las apps que admiten mensajes pueden extender las notificaciones correspondientes para permitir que Android Auto las procese cuando se esté ejecutando. Estas notificaciones se muestran en Android Auto y les permiten a los usuarios leer y responder los mensajes en una interfaz coherente y con mínima distracción. Además, cuando usas la API de MessagingStyle, recibes notificaciones de mensajes optimizadas para todos los dispositivos Android, incluido Android Auto. Las optimizaciones incluyen una IU especializada para las notificaciones de mensajes, animaciones mejoradas y compatibilidad con imágenes intercaladas.
En esta guía, se muestra cómo extender una app que muestra mensajes al usuario y recibe sus respuestas, como una app de chat, para enviar el mensaje y responderlo en un dispositivo Auto. Si deseas obtener orientación relacionada con el diseño, consulta la sección sobre apps de mensajería en el sitio de Design for Driving.
Primeros pasos
Para proporcionar un servicio de mensajería para dispositivos Auto, tu app debe declarar su compatibilidad con Android Auto en el manifiesto y poder hacer lo siguiente:
- Compilar y enviar objetos
NotificationCompat.MessagingStyle
que contengan objetosAction
de respuesta y marcado como leído - Controlar cómo responder una conversación y marcarla como leída con un
Service
Conceptos y objetos
Antes de comenzar a diseñar tu app, resulta útil comprender cómo controla la mensajería Android Auto.
Una comunicación individual se denomina mensaje y se representa mediante la clase MessagingStyle.Message
. Un mensaje consta de un remitente, el contenido y la hora a la que se envió.
La comunicación entre los usuarios se denomina conversación y la representa un objeto MessagingStyle
. Una conversación, o MessagingStyle
, contiene un título, los mensajes y si la conversación es entre un grupo de usuarios.
Para notificar a los usuarios sobre las actualizaciones de una conversación (como un mensaje nuevo), las apps publican una Notification
en el sistema Android.
Esta Notification
usa el objeto MessagingStyle
para mostrar la IU específica de la mensajería en el panel de notificaciones. La plataforma de Android también pasa esta Notification
a Android Auto, y el MessagingStyle
se extrae y se usa para publicar una notificación en la pantalla del vehículo.
Android Auto también requiere que las apps agreguen objetos Action
a una Notification
para permitir que el usuario responda rápidamente un mensaje o lo marque como leído directo desde el panel de notificaciones.
En resumen, una sola conversación está representada por un objeto Notification
que tiene el estilo de un objeto MessagingStyle
. El MessagingStyle
contiene todos los mensajes que están dentro de esa conversación en uno o más objetos MessagingStyle.Message
. Además, para que sea compatible con Android Auto, una app debe adjuntar a la Notification
objetos Action
de respuesta y marcado como leído.
Flujo de mensajes
En esta sección, se describe un flujo de mensajes típico entre tu app y Android Auto.
- Tu app recibe un mensaje.
- Tu app genera una notificación
MessagingStyle
con objetosAction
de respuesta y marcado como leído. - Android Auto recibe el evento "notificación nueva" del sistema Android y encuentra el
MessagingStyle
, laAction
de respuesta y laAction
de marcado como leído. - Android Auto genera y muestra una notificación en el vehículo.
- Si el usuario presiona la notificación en la pantalla del vehículo, Android Auto activa la
Action
de marcado como leído.- En segundo plano, tu app debe controlar este evento, que consiste en marcar el mensaje como leído.
- Si el usuario responde a la notificación por voz, Android Auto incluye una transcripción de la respuesta del usuario en la
Action
de respuesta y, luego, la activa.- En segundo plano, tu app debe controlar este evento de respuesta.
Suposiciones preliminares
En esta página, no encontrarás una guía para crear una app de mensajería completa. En la siguiente muestra de código, se incluyen algunos de los elementos que necesita tu app antes de comenzar a admitir mensajes en 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)
Cómo declarar la compatibilidad con Android Auto
Cuando Android Auto recibe una notificación de una app de mensajes, verifica que la app haya declarado su compatibilidad con Android Auto. Para habilitar esa compatibilidad, incluye la siguiente entrada en el manifiesto de tu app:
<application>
...
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/>
...
</application>
Esta entrada de manifiesto hace referencia a otro archivo en formato XML que debes crear con la siguiente ruta de acceso: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml
.
En automotive_app_desc.xml
, debes declarar las capacidades de Android Auto que admite tu app. Por ejemplo, para declarar la compatibilidad con notificaciones, incluye lo siguiente:
<automotiveApp>
<uses name="notification" />
</automotiveApp>
Si tu app se puede configurar como el controlador de SMS predeterminado, asegúrate de incluir el siguiente elemento <uses>
. Si no lo haces, se usará un controlador predeterminado integrado en Android Auto para controlar los mensajes SMS/MMS entrantes cuando la app esté configurada como el controlador de SMS predeterminado, lo que puede generar la duplicación de notificaciones.
<automotiveApp>
...
<uses name="sms" />
</automotiveApp>
Cómo importar la biblioteca principal de AndroidX
La compilación de notificaciones para uso con dispositivos Auto requiere la biblioteca principal de AndroidX. Importa la biblioteca a tu proyecto de la siguiente manera:
- En el archivo
build.gradle
de nivel superior, incluye una dependencia en el repositorio de Maven de Google, como se muestra en el siguiente ejemplo:
Groovy
allprojects { repositories { google() } }
Kotlin
allprojects { repositories { google() } }
- En el archivo
build.gradle
del módulo de tu app, incluye la dependencia de la biblioteca de AndroidX Core, como se muestra en el siguiente ejemplo:
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") }
Cómo controlar las acciones del usuario
Tu app de mensajería debe tener un método de control de las actualizaciones de una conversación mediante una Action
. Para Android Auto, hay dos tipos de objetos Action
que tu app deberá controlar: responder y marcar el mensaje como leído. Recomendamos manejarlos con un IntentService
, que proporciona la flexibilidad para controlar llamadas potencialmente costosas en segundo plano, lo que libera el subproceso principal de la app.
Cómo definir las acciones de intent
Las acciones de Intent
son cadenas simples que identifican para qué sirve la clase Intent
.
Debido a que un solo servicio puede controlar múltiples tipos de intents, es más fácil definir múltiples cadenas de acción en lugar de definir múltiples componentes de IntentService
.
La app de mensajería de ejemplo en esta guía tiene los dos tipos de acciones requeridas: responder y marcar el mensaje como leído, como se muestra en la siguiente muestra de código.
private const val ACTION_REPLY = "com.example.REPLY"
private const val ACTION_MARK_AS_READ = "com.example.MARK_AS_READ"
Cómo crear el servicio
Para crear un servicio que controle estos objetos Action
, necesitas el ID de la conversación, que es una estructura de datos arbitraria definida por la app y que identifica la conversación. También necesitas una clave de entrada remota, que se analiza en detalle más adelante en esta sección. En la siguiente muestra de código, se crea un servicio para manejar las acciones requeridas:
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 asociar este servicio con tu app, también debes registrarlo en el manifiesto de tu app, como se muestra en el siguiente ejemplo:
<application>
<service android:name="com.example.MessagingService" />
...
</application>
Cómo generar y controlar intents
No hay forma de que otras apps, incluida Android Auto, obtengan el Intent
que activa el MessagingService
, ya que los objetos Intent
se pasan a otras apps con un PendingIntent
. Debido a esta limitación, debes crear un objeto RemoteInput
para permitir que otras apps proporcionen el texto de respuesta a tu app, como se muestra en el siguiente ejemplo:
/**
* 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
}
En la cláusula de cambio ACTION_REPLY
dentro de MessagingService
, extrae la información que se incluye en el Intent
de respuesta, como se muestra en el siguiente ejemplo:
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)
}
El Intent
de marcado como leído se controla de una manera similar. Sin embargo, no es necesario usar un RemoteInput
, como se muestra en el siguiente ejemplo:
/** 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 cláusula de cambio ACTION_MARK_AS_READ
dentro de MessagingService
no requiere más lógica, como se muestra en el siguiente ejemplo:
// Marking as read has no other logic.
ACTION_MARK_AS_READ -> conversation.markAsRead()
Cómo notificar a los usuarios sobre los mensajes
Una vez que se complete el manejo de las acciones de conversación, el siguiente paso es generar notificaciones compatibles con Android Auto.
Cómo crear acciones
Los objetos Action
se pueden pasar a otras apps con una Notification
para activar métodos en la app original. Así es como Android Auto puede marcar una conversación como leída o responderla.
Para crear una Action
, comienza con un Intent
. En el siguiente ejemplo, se muestra cómo crear un Intent
de "respuesta":
fun createReplyAction(
context: Context, appConversation: YourAppConversation): Action {
val replyIntent: Intent = createReplyIntent(context, appConversation)
// ...
Luego, une este Intent
en un PendingIntent
que lo prepara para el uso de una app externa. Un PendingIntent
bloquea todo el acceso al Intent
unido, ya que solo expone un conjunto seleccionado de métodos que permiten que la app receptora active el Intent
y obtenga el nombre del paquete de la app de origen. La app externa nunca puede acceder al Intent
subyacente ni a los datos que contenga.
// ...
val replyPendingIntent = PendingIntent.getService(
context,
createReplyId(appConversation), // Method explained later.
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
// ...
Antes de configurar la Action
de respuesta, ten en cuenta que Android Auto tiene tres requisitos para la Action
de respuesta:
- La acción semántica debe configurarse como
Action.SEMANTIC_ACTION_REPLY
. - La
Action
debe indicar que no mostrará ninguna interfaz de usuario cuando se active. - La
Action
debe contener un soloRemoteInput
.
En la siguiente muestra de código, se configura una Action
de respuesta que aborda los requisitos enumerados arriba:
// ...
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
}
La acción de marcar como leído es similar, excepto que no hay RemoteInput
.
Por lo tanto, Android Auto tiene dos requisitos para la Action
de marcado como leído:
- La acción semántica se establece en
Action.SEMANTIC_ACTION_MARK_AS_READ
. - La acción indica que no mostrará ninguna interfaz de usuario cuando se active.
En la siguiente muestra de código, se configura una Action
de marcado como leído, que cumple con estos 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
}
Cuando se generan los intents pendientes, se usan dos métodos: createReplyId()
y createMarkAsReadId()
. Estos métodos sirven como códigos de solicitud para cada PendingIntent
, que Android utiliza en el control de intents pendientes existentes. Los métodos create()
deben mostrar IDs únicos para cada conversación, pero las llamadas repetidas de la misma conversación deben mostrar el ID único que ya se generó.
Considera un ejemplo con dos conversaciones, A y B: el ID de respuesta de la conversación A es 100 y su ID de marcado como leído es 101. El ID de respuesta de la conversación B es 102 y su ID de marcado como leído es 103. Si se actualiza la conversación A, los IDs de respuesta y marcado como leído siguen siendo 100 y 101. Para obtener más información, consulta PendingIntent.FLAG_UPDATE_CURRENT
.
Cómo crear una instancia de MessagingStyle
MessagingStyle
es el proveedor de la información de mensajería y lo que Android Auto usa para leer en voz alta cada uno de los mensajes de una conversación.
Primero, se debe especificar el usuario del dispositivo en la forma de un objeto Person
, como se muestra en el siguiente ejemplo:
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()
// ...
Luego, puedes crear el objeto MessagingStyle
y proporcionar detalles sobre la conversación.
// ...
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 último, agrega los mensajes no leídos.
// ...
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
}
Cómo empaquetar y enviar la notificación
Después de generar los objetos Action
y MessagingStyle
, puedes crear y publicar 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)
}
Recursos adicionales
Informa un problema en las apps de mensajería para Android Auto
Si tienes un problema mientras desarrollas tu app de mensajería para Android Auto, puedes informarlo con la herramienta de seguimiento de errores de Google. Asegúrate de completar toda la información solicitada en la plantilla de problemas.
Antes de informar un problema nuevo, verifica si ya se informó en la lista de problemas. Para suscribirte a un problema o votarlo, haz clic en el ícono de estrella que aparece en la herramienta de seguimiento. Si deseas obtener más información, consulta Cómo suscribirte a un problema.