Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Cómo crear apps de mensajes para Android Auto

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 marco de trabajo de Android permite que las apps de mensajes extiendan sus servicios a la experiencia de manejo utilizando una interfaz de usuario estándar que les permite a los conductores mantener sus ojos centrados en la carretera.

Las apps que admiten mensajes pueden extender las notificaciones correspondientes para permitir que Android Auto las consuma 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 MessagingStyle API, obtienes el beneficio de las notificaciones mensajes optimizadas para todos los dispositivos Android, incluido Android Auto. Estas optimizaciones incluyen la IU especializada para las notificaciones de mensajes, animaciones mejoradas y compatibilidad con imágenes intercaladas.

En esta lección, se supone que compilaste una app que muestra mensajes al usuario y recibe sus respuestas, como una app de chat. Se mostrará cómo extender tu app para entregar esos mensajes a un dispositivo con Android Auto a fin de que se muestren y se respondan.

Cómo comenzar

Para habilitar tu app para que proporcione el servicio de mensajes para los dispositivos con Android Auto, esta debe tener la capacidad de realizar las siguientes tareas:

  1. Compila y envía objetos de NotificationCompat.MessagingStyle que contengan objetos de Action de respuesta y marca.
  2. Controla cómo responder una conversación y marcarla como leída con un Service.
  3. Configura tu manifiesto para indicar que la app es compatible con Android Auto.

Conceptos y objetos

Antes de comenzar a diseñar tu app, es útil comprender cómo Android Auto controla los mensajes.

Un fragmento individual de comunicación se denomina mensaje y está representado por la clase MessagingStyle.Message. Un mensaje contiene un remitente, el contenido del mensaje y la hora en la que se envió el mensaje.

La comunicación entre los usuarios se denomina conversación y está representado por un objeto MessagingStyle. Una conversación (o un MessagingStyle) contiene un título, los mensajes y si la conversación es entre un grupo (es decir, la conversación tiene más de un destinatario).

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 notificación 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.

Las apps también pueden agregar objetos de Action a una Notification para permitir al usuario responder rápidamente a un mensaje o marcarlo como leído de forma directa desde el panel de notificaciones. Android Auto requiere los objetos Action para responder y marcar como leídos con el fin de administrar una conversación.

En resumen, una única conversación está representada por un solo objeto Notification que tiene un estilo con un único objeto MessagingStyle. El elemento MessagingStyle contiene todos los mensajes dentro de esa conversación con uno o más objetos MessagingStyle.Message. Finalmente, para que sea totalmente compatible con Android Auto, debes adjuntar una respuesta y marcar como leída la Action en la Notification.

Flujo de mensajes

En esta sección, se describe un flujo de mensajes típico entre tu app y Android Auto.

  1. Tu app recibe un mensaje.
  2. Luego, tu app genera una notificación de MessagingStyle con una respuesta y una Action para marcar el mensaje como no leído.
  3. Android Auto recibe el evento "notificación nueva" desde el sistema Android y encuentra el MessagingStyle de respuesta de la Action y la Action para marcar el mensaje como leído.
  4. Android Auto genera y muestra una notificación en el vehículo.
  5. Si un usuario presiona la notificación en la pantalla del vehículo, Android Auto activa la Action
      para marcar el mensaje como leído.
    • En segundo plano, tu app debe controlar este evento, que consiste en marcar el mensaje como leído.
  6. 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, la app debe controlar este evento de respuesta.

Suposiciones preliminares

En esta página, no encontrarás una guía para crear una app de mensajes completa. Sin embargo, el ejemplo de código a continuación incluye algunos de los aspectos que tu app debería tener 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)
    

Declara 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 esta 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 XML que debes crear con la siguiente ruta: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml, que declara qué capacidades de Android Auto son compatibles con tu app. Por ejemplo, con el propósito de incluir asistencia para las notificaciones, incluye lo siguiente en tu automotive_app_desc.xml:

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

Si tu app requiere compatibilidad para controlar SMS, MMS y RCS, también debes incluir lo siguiente:

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

Importa la biblioteca principal de AndroidX

La compilación de notificaciones para uso con dispositivos de Android Auto requiere la biblioteca principal de AndroidX y puedes importarla a tu proyecto de la siguiente manera:

  1. En el archivo build.gradle de nivel superior, asegúrate de incluir el repositorio Maven de Google, como se muestra a continuación.

    allprojects {
            repositories {
                google()
            }
        }
        
  2. En el archivo build.gradle del módulo de tu app, incluye la dependencia de la biblioteca principal de AndroidX, como se muestra a continuación:

    dependencies {
            implementation 'androidx.core:core:1.0.0'
        }
        

Controla las acciones del usuario

Tu app de mensajes debe tener un método de control de las actualizaciones de una conversación mediante una instancia de Action. Para Android Auto, hay dos tipos de objetos de Action que tu app debe controlar: responder y marcar el mensaje como leído. La forma recomendada de hacerlo es mediante el uso de una instancia de IntentService. Un IntentServiceproporciona la flexibilidad de controlar las llamadas que posiblemente sean costosas en segundo plano y libera el subproceso principal de una app.

Define las acciones de intent

Las acciones de Intent (que no se deben confundir con las acciones de notificación) son strings simples que identifican para qué sirve la instancia de Intent. Debido a que un solo servicio puede controlar varios tipos de intents, es más fácil definir varias strings de Intent.action, en lugar de definir varios IntentServices.

En nuestra app de mensajes de ejemplo, tenemos dos tipos de acciones: responder y marcar el mensaje como leído, como se declara en el siguiente ejemplo de código.

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

Crea el servicio

Para crear un servicio que controle estas 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, y una clave de entrada remota, que se analiza en detalle más adelante en esta sección. En el siguiente ejemplo de código crea ese servicio.

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?) {
            // Fetch our internal data
            val conversationId = intent!!.getIntExtra(EXTRA_CONVERSATION_ID_KEY, -1)

            // And search our database for that conversation
            val conversation = YourAppConversation.getById(conversationId)

            // Then handle the action that was requested in the intent. The TODOs
            // are addressed in a section below.
            when (intent.action) {
                ACTION_REPLY -> TODO()
                ACTION_MARK_AS_READ -> TODO()
            }
        }
    }
    

Para asociar este servicio con tu app, también debes registrar el servicio en el manifiesto de tu app, como se muestra a continuación.

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

Genera y controla las instancias de intent

No hay forma de que otras apps obtengan el Intent que activa el MessagingService, ya que cada Intent se pasa a apps externas a través de un PendingIntent. Debido a esta limitación, debes crear un objeto RemoteInput para permitir que esas otras apps proporcionen el texto de "respuesta" a tu app, como se muestra a continuación.

/**
     * Creates a [RemoteInput] which allows remote apps to 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 via
        // static methods in the RemoteInput class.
    }

    /** Creates an [Intent] which handles replying to the given [appConversation]. */
    fun createReplyIntent(
            context: Context, appConversation: YourAppConversation): Intent {
        // Create the intent backed by the MessagingService.
        val intent = Intent(context, MessagingService::class.java)

        // This action string lets the MessagingService know this is a "reply" request.
        intent.action = ACTION_REPLY

        // Provide the conversation id so we know what conversation this applies to.
        intent.putExtra(EXTRA_CONVERSATION_ID_KEY, appConversation.id)

        return intent
    }
    

Ahora que sabes lo que sucede en el Intent de respuesta, aborda TODO para la cláusula de cambio ACTION_REPLY dentro de MessagingService y extrae esa información de la siguiente manera:

ACTION_REPLY -> {
        // Extract reply response from the intent using the same key that the
        // above RemoteInput used.
        val results: Bundle = RemoteInput.getResultsFromIntent(intent)
        val message = results.getString(REMOTE_INPUT_RESULT_KEY)

        // Note this conversation object came from above in the MessagingService
        conversation.reply(message)
    }
    

El Intent para marcar el mensaje como leído se controla de una manera similar (sin embargo, no requiere una instancia de RemoteInput).

/** Creates an [Intent] which 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
    }
    

El siguiente ejemplo de código aborda el TODO para la cláusula de cambio ACTION_MARK_AS_READ dentro de MessagingService:

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

Notifica a los usuarios sobre los mensajes

Una vez listo el control de las acciones de conversación de la app de mensajes, podemos generar notificaciones que cumplan con Android Auto.

Crea acciones

Las Action son objetos que se pueden pasar a otras apps a través de una Notification para activar métodos en la app original. Así es como Android Auto puede marcar una conversación como leída y responder a ella.

Para crear una Action, comienza con un Intent, como se muestra en el siguiente Intent de "respuesta":

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

Luego, unimos este Intent en un PendingIntent que lo prepara para el uso externo de la app. Un PendingIntent bloquea todo el acceso al Intent unido cuando se expone solamente un conjunto selecto de métodos que permiten que una app receptora active el Intent u obtenga el nombre del paquete de la app de origen, pero nunca permite que la app externa acceda al Intent subyacente o a los datos que contiene.

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

Antes de configurar la Actionde 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 solo RemoteInput.

En el siguiente ejemplo de código, se configura la Actionde respuesta y se abordan los requisitos enumerados arriba:

    // ...
        val replyAction = Action.Builder(R.drawable.reply, "Reply", replyPendingIntent)
            // Provide context to what firing Action does.
            .setSemanticAction(Action.SEMANTIC_ACTION_REPLY)

            // The action doesn't show any UI (it's a requirement for apps to
            // not show UI for 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
    }
    

En la acción para marcar una respuesta como leída, hacemos lo mismo, excepto que no hay ninguna instancia de RemoteInput. Por lo tanto, Android Auto tiene 2 requisitos para la Action que marca el mensaje 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.
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)
        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
    }
    

Crea una instancia de MessagingStyle

MessagingStyle es el proveedor de la información de mensajes 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.

fun createMessagingStyle(
            context: Context, appConversation: YourAppConversation): MessagingStyle {
        // Method defined by our 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)

        // Set the conversation title. Note that if your app's target version is lower
        // than P, this will automatically mark this conversation as a group (to
        // maintain backwards compatibility). Simply 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 via a Person object
            val senderPerson = Person.Builder()
                .setName(appMessage.sender.name)
                .setIcon(appMessage.sender.icon)
                .setKey(appMessage.sender.id)
                .build()

            // And the message is added. Note that more complex messages (ie. images)
            // may be created and added by instantiating the MessagingStyle.Message
            // class directly. See documentation for details.
            messagingStyle.addMessage(
                    appMessage.body, appMessage.timeReceived, senderPerson)
        }

        return messagingStyle
    }
    

Empaqueta y envía la notificación

Después de generar los objetos Action y MessagingStyle, puedes crear y publicar la Notification.

fun notify(context: Context, appConversation: YourAppConversation) {
        // Create our Actions and MessagingStyle from the methods defined above.
        val replyAction = createReplyAction(context, appConversation)
        val markAsReadAction = createMarkAsReadAction(context, appConversation)
        val messagingStyle = createMessagingStyle(context, appConversation)

        // Create the notification.
        val notification = NotificationCompat.Builder(context, channel)
            // A required field for the Android UI.
            .setSmallIcon(R.drawable.notification_icon)

            // Shown in Android Auto as the conversation image.
            .setLargeIcon(appConversation.icon)

            // Add MessagingStyle.
            .setStyle(messagingStyle)

            // Add reply action.
            .addAction(replyAction)

            // Let's say we don't want to show our mark-as-read Action in the
            // notification shade. We can do that by adding it as invisible so it
            // won't appear in Android UI, but still satisfy Android Auto's
            // mark-as-read Action requirement. You're free to add both Actions as
            // visible or invisible. This is just a stylistic choice.
            .addInvisibleAction(markAsReadAction)

            .build()

        // Post the notification for the user to see.
        val notificationManagerCompat = NotificationManagerCompat.from(context)
        notificationManagerCompat.notify(appConversation.id, notification)
    }
    

Consulta también