Распространить уведомления о сообщениях на Android Auto

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

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

Соответствующие рекомендации по дизайну см. в разделе «Приложения для обмена сообщениями» на сайте Design for Driving.

Начать

Чтобы обеспечить службу обмена сообщениями для устройств 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 приложение должно прикреплять объекты Action «ответить» и «отметить как прочитанное» к Notification .

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

В этом разделе описывается типичный поток сообщений между вашим приложением и 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 включите зависимость от репозитория Maven от Google, как показано в следующем примере:

Круто

allprojects {
    repositories {
        google()
    }
}

Котлин

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

Круто

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

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

Котлин

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

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

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

Вашему приложению для обмена сообщениями нужен способ обработки обновления разговора через 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 . Обязательно заполните всю запрашиваемую информацию в шаблоне проблемы.

Создать новый выпуск

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