Как добавить уведомления о сообщениях в Android Auto

description: Расширьте уведомления о сообщениях для Android Auto с помощью MessagingStyle API. keywords_public: Android Auto,уведомления о сообщениях,MessagingStyle,NotificationCompat.Action,приложения для общения,разработка для автомобилей,IntentService,RemoteInput,PendingIntent

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

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

Рекомендации по дизайну вы найдете в разделе Приложения для общения на портале Design for Cars.

Начать

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

Понятия и объекты

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

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

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

Чтобы уведомить пользователей об обновлениях в чате, например о новом сообщении, приложения отправляют в систему Android объект Notification. Этот 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-файл, automotive_app_desc.xml, который необходимо создать в res/xml каталоге модуля приложения. В файле automotive_app_desc.xml объявите функции Android Auto, которые поддерживает ваше приложение. Чтобы объявить о поддержке уведомлений, включите следующее:

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

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

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

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

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

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

Яркий

allprojects {
    repositories {
        google()
    }
}

Kotlin

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

Яркий

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

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

Kotlin

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

    // If your app is written in Kotlin
    implementation("androidx.core:core-ktx:1.19.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, потому что намерения передаются другим приложениям через 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
}

В операторе switch 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
}

Оператор switch 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 с помощью метода createReplyIntent() из предыдущего раздела.

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. Поэтому к объекту 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 later.
            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. Идентификатор ответа чата Б – 102, а идентификатор пометки как прочитанного – 103. Если цепочка A обновляется, идентификаторы ответа и пометки как прочитанного по-прежнему равны 100 и 101. Дополнительную информацию вы найдете в статье PendingIntent.FLAG_UPDATE_CURRENT.

Как создать MessagingStyle

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

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

Создать новую проблему

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