Создание приложений для обмена сообщениями для Android Auto

Категория «Коммуникации» скоро появится
Категория «Сообщения» расширяется и включает поддержку новых возможностей, включая историю сообщений и возможности звонков.

Оставаться на связи с помощью сообщений важно для многих водителей. Приложения чата могут сообщать пользователям, нужно ли забирать ребенка или изменилось ли место ужина. Платформа Android позволяет приложениям для обмена сообщениями расширять свои услуги в процессе вождения, используя стандартный пользовательский интерфейс, который позволяет водителям следить за дорогой.

Приложения, поддерживающие обмен сообщениями, могут расширять свои уведомления о сообщениях, чтобы позволить Android Auto использовать их во время работы Auto. Эти уведомления отображаются в режиме «Авто» и позволяют пользователям читать сообщения и отвечать на них в едином, не отвлекающем от внимания интерфейсе. А когда вы используете API MessagingStyle , вы получаете оптимизированные уведомления о сообщениях для всех устройств Android, включая Android Auto. Оптимизации включают в себя пользовательский интерфейс, предназначенный для уведомлений о сообщениях, улучшенную анимацию и поддержку встроенных изображений.

В этом руководстве показано, как расширить приложение, которое отображает сообщения пользователю и получает ответы пользователя, например приложение чата, чтобы передавать отображение сообщений и получение ответов на автоматическое устройство. Соответствующие рекомендации по проектированию см. в разделе «Приложения для обмена сообщениями» на сайте «Дизайн для вождения».

Начать

Чтобы предоставить службу обмена сообщениями для устройств Auto, ваше приложение должно заявить о поддержке Android Auto в манифесте и иметь возможность выполнять следующие действия:

  • Создайте и отправьте объекты NotificationCompat.MessagingStyle , содержащие объекты ответа и Action , помеченные как прочитанные.
  • Обработка ответа и пометка разговора как прочитанного с помощью Service .

Концепции и объекты

Прежде чем приступить к разработке приложения, полезно понять, как Android Auto обрабатывает сообщения.

Отдельный фрагмент сообщения называется сообщением и представлен классом MessagingStyle.Message . Сообщение содержит отправителя, содержимое сообщения и время отправки сообщения.

Общение между пользователями называется беседой и представлено объектом MessagingStyle . Разговор или MessagingStyle содержит заголовок, сообщения и сведения о том, ведется ли разговор среди группы пользователей.

Чтобы уведомить пользователей об обновлениях беседы, например о новом сообщении, приложения отправляют Notification в систему Android. Это Notification использует объект MessagingStyle для отображения пользовательского интерфейса, специфичного для обмена сообщениями, в тени уведомлений. Платформа Android также передает это Notification в Android Auto, а MessagingStyle извлекается и используется для публикации уведомления на дисплее автомобиля.

Android Auto также требует, чтобы приложения добавляли объекты Action в Notification , чтобы пользователь мог быстро ответить на сообщение или пометить его как прочитанное непосредственно из панели уведомлений.

Таким образом, один диалог представлен объектом Notification , стиль которого определяется объектом MessagingStyle . MessagingStyle содержит все сообщения в этом диалоге в одном или нескольких объектах MessagingStyle.Message . А чтобы соответствовать требованиям Android Auto, приложение должно прикреплять к Notification объекты ответа и Action как прочитанные.

Поток сообщений

В этом разделе описан типичный поток сообщений между вашим приложением и Android Auto.

  1. Ваше приложение получает сообщение.
  2. Ваше приложение генерирует уведомление MessagingStyle с объектами ответа и пометки как Action .
  3. Android Auto получает событие «новое уведомление» от системы Android и находит MessagingStyle , Action ответа и Action пометки как прочитанное.
  4. Android Auto генерирует и отображает уведомление в автомобиле.
  5. Если пользователь касается уведомления на дисплее автомобиля, Android Auto запускает Action «Отметить как прочитанное».
    • В фоновом режиме ваше приложение должно обрабатывать это событие пометки как прочитанное.
  6. Если пользователь отвечает на уведомление с помощью голоса, Android Auto помещает расшифровку ответа пользователя в Action ответа, а затем запускает его.
    • В фоновом режиме ваше приложение должно обрабатывать это событие ответа.

Предварительные предположения

Эта страница не поможет вам создать целое приложение для обмена сообщениями. Следующий пример кода включает в себя некоторые вещи, необходимые вашему приложению, прежде чем вы начнете поддерживать обмен сообщениями с помощью 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)

Заявить о поддержке Android Auto

Когда Android Auto получает уведомление от приложения для обмена сообщениями, он проверяет, заявило ли приложение о поддержке Android Auto. Чтобы включить эту поддержку, включите следующую запись в манифест вашего приложения:

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

Эта запись манифеста относится к другому XML-файлу, который необходимо создать, по следующему пути: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml . В automotive_app_desc.xml укажите возможности Android Auto, которые поддерживает ваше приложение. Например, чтобы объявить поддержку уведомлений, включите следующее:

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

Если ваше приложение можно установить в качестве обработчика SMS по умолчанию , обязательно включите следующий элемент <uses> . Если вы этого не сделаете, для обработки входящих SMS/MMS-сообщений будет использоваться встроенный в Android Auto обработчик по умолчанию, когда ваше приложение установлено в качестве обработчика SMS по умолчанию, что может привести к дублированию уведомлений.

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

Импортируйте основную библиотеку AndroidX.

Для создания уведомлений для использования с устройствами Auto требуется основная библиотека AndroidX . Импортируйте библиотеку в свой проект следующим образом:

  1. В файле build.gradle верхнего уровня включите зависимость от репозитория Google Maven, как показано в следующем примере:

классный

allprojects {
    repositories {
        google()
    }
}

Котлин

allprojects {
    repositories {
        google()
    }
}
  1. В файл build.gradle вашего модуля приложения включите зависимость библиотеки AndroidX Core , как показано в следующем примере:

классный

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

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

Котлин

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

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

Обрабатывать действия пользователя

Вашему приложению для обмена сообщениями нужен способ обработки обновления разговора с помощью Action . В Android Auto существует два типа объектов Action , которые ваше приложение должно обрабатывать: ответ и пометка как прочитанные. Мы рекомендуем обрабатывать их с помощью IntentService , который обеспечивает гибкость обработки потенциально дорогостоящих вызовов в фоновом режиме , освобождая основной поток вашего приложения.

Определить действия по намерению

Действия Intent — это простые строки, которые определяют, для чего предназначено Intent . Поскольку одна служба может обрабатывать несколько типов намерений, проще определить несколько строк действий, чем определять несколько компонентов IntentService .

В примере приложения для обмена сообщениями в этом руководстве предусмотрены два обязательных типа действий: ответить и пометить как прочитанное, как показано в следующем примере кода.

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

Создать сервис

Чтобы создать службу, которая обрабатывает эти объекты Action , вам понадобится идентификатор диалога, который представляет собой произвольную структуру данных, определенную вашим приложением и идентифицирующую диалог. Вам также понадобится клавиша удаленного ввода, которая подробно обсуждается далее в этом разделе. В следующем примере кода создается служба для выполнения необходимых действий:

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

Чтобы связать эту службу с вашим приложением, вам также необходимо зарегистрировать ее в манифесте вашего приложения, как показано в следующем примере:

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

Генерация и обработка намерений

Другие приложения, включая Android Auto, не могут получить Intent , который запускает MessagingService , поскольку Intent передаются другим приложениям через PendingIntent . Из-за этого ограничения вам необходимо создать объект RemoteInput , чтобы другие приложения могли возвращать текст ответа вашему приложению, как показано в следующем примере:

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

В предложении переключателя ACTION_REPLY в MessagingService извлеките информацию, которая входит в Intent ответа, как показано в следующем примере:

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

Вы обрабатываете Intent пометить как прочитанное аналогичным образом. Однако для этого не требуется RemoteInput , как показано в следующем примере:

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

Предложение переключателя ACTION_MARK_AS_READ в MessagingService не требует дополнительной логики, как показано в следующем примере:

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

Уведомлять пользователей о сообщениях

После завершения обработки действий диалога следующим шагом будет создание уведомлений, совместимых с Android Auto.

Создание действий

Объекты Action можно передавать другим приложениям с помощью Notification для запуска методов в исходном приложении. Таким образом Android Auto может пометить разговор как прочитанный или ответить на него.

Чтобы создать Action , начните с Intent . В следующем примере показано, как создать Intent «ответить»:

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

Затем оберните это Intent в PendingIntent , что подготовит его для использования внешним приложением. PendingIntent блокирует весь доступ к обернутому Intent предоставляя только выбранный набор методов, которые позволяют принимающему приложению активировать Intent или получить имя пакета исходного приложения. Внешнее приложение никогда не сможет получить доступ к базовому Intent или данным внутри него.

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

Прежде чем настраивать Action ответа, имейте в виду, что Android Auto предъявляет три требования к Action ответа:

  • Семантическое действие должно быть установлено в Action.SEMANTIC_ACTION_REPLY .
  • Action должно указывать, что при его запуске не будет отображаться пользовательский интерфейс.
  • Action должно содержать один RemoteInput .

Следующий пример кода устанавливает ответное Action , соответствующее требованиям, перечисленным выше:

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

Обработка действия «Пометить как прочитанное» аналогична, за исключением того, что RemoteInput отсутствует. Поэтому Android Auto предъявляет два требования к Action «Пометить как прочитанное»:

  • Семантическое действие установлено на Action.SEMANTIC_ACTION_MARK_AS_READ .
  • Действие указывает на то, что при запуске не будет отображаться пользовательский интерфейс.

В следующем примере кода устанавливается Action «Пометить как прочитанное», отвечающее этим требованиям:

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
}

При генерации ожидающих намерений используются два метода: createReplyId() и createMarkAsReadId() . Эти методы служат кодами запроса для каждого PendingIntent , которые используются Android для управления существующими ожидающими намерениями. Методы create() должны возвращать уникальные идентификаторы для каждого диалога, но повторные вызовы для одного и того же диалога должны возвращать уже сгенерированный уникальный идентификатор.

Рассмотрим пример с двумя разговорами, A и B: идентификатор ответа разговора A равен 100, а его идентификатор пометки как прочитанного — 101. Идентификатор ответа разговора B — 102, а его идентификатор пометки как прочитанный — 103. Если разговор A обновляется, идентификаторы ответа и пометки как прочитанные по-прежнему равны 100 и 101. Дополнительные сведения см. в PendingIntent.FLAG_UPDATE_CURRENT .

Создайте стиль сообщений

MessagingStyle — это носитель информации о сообщениях, который Android Auto использует для чтения вслух каждого сообщения в разговоре.

Во-первых, пользователь устройства должен быть указан в форме объекта Person , как показано в следующем примере:

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

Затем вы можете создать объект MessagingStyle и предоставить некоторые сведения о разговоре.

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

Наконец, добавьте непрочитанные сообщения.

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

Упакуйте и отправьте уведомление

После создания объектов Action и MessagingStyle вы можете создать и опубликовать 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)
}

Дополнительные ресурсы

Сообщить о проблеме с Android Auto Messaging

Если вы столкнулись с проблемой при разработке приложения для обмена сообщениями для Android Auto, вы можете сообщить об этом с помощью Google Issue Tracker . Обязательно заполните всю запрашиваемую информацию в шаблоне вопроса.

Создать новую задачу

Прежде чем подавать новую проблему, проверьте, есть ли о ней уже сообщение в списке проблем. Вы можете подписаться и проголосовать за проблемы, щелкнув звездочку проблемы в трекере. Дополнительную информацию см. в разделе «Подписка на выпуск» .