Membuat aplikasi pesan untuk Android Auto

Tetap terhubung melalui pesan menjadi kebutuhan penting bagi banyak pengemudi. Aplikasi chat dapat memberi tahu pengguna jika ada anak yang perlu dijemput atau jika lokasi makan malam berubah. Framework Android memungkinkan aplikasi pesan memperluas layanannya ke dalam pengalaman mengemudi menggunakan antarmuka pengguna standar yang memungkinkan pengemudi tetap memfokuskan perhatian ke jalan.

Aplikasi yang mendukung pesan dapat mengirim notifikasi pesannya ke Android Auto untuk digunakan saat Auto sedang berjalan. Notifikasi ini ditampilkan di Auto dan memungkinkan pengguna untuk membaca dan membalas pesan dalam antarmuka yang konsisten dan minim gangguan. Selain itu, jika menggunakan MessagingStyle API, Anda akan mendapatkan notifikasi pesan yang dioptimalkan untuk semua perangkat Android, termasuk Android Auto. Pengoptimalan mencakup UI yang dikhususkan untuk notifikasi pesan, animasi yang ditingkatkan, dan dukungan untuk gambar inline.

Panduan ini menunjukkan cara memperluas aplikasi yang menampilkan pesan kepada pengguna dan menerima balasan pengguna, seperti aplikasi chat, untuk memberikan tampilan pesan dan membalas tanda terima ke perangkat Auto. Untuk panduan desain terkait, lihat Aplikasi pesan di situs Design for Driving.

Mulai

Untuk menyediakan layanan pesan bagi perangkat Auto, aplikasi Anda harus mendeklarasikan dukungannya untuk Android Auto dalam manifes dan dapat melakukan hal berikut:

  • Membuat dan mengirim objek NotificationCompat.MessagingStyle yang berisi objek Action balas dan tandai sudah dibaca.
  • Menangani proses membalas pesan dan menandai percakapan sebagai sudah dibaca dengan Service.

Konsep dan objek

Sebelum mulai mendesain aplikasi, sebaiknya pahami terlebih dahulu cara Android Auto menangani pesan.

Setiap bagian komunikasi disebut sebagai pesan dan direpresentasikan oleh class MessagingStyle.Message. Pesan menyertakan informasi pengirim, isi pesan, dan waktu pengiriman.

Komunikasi antarpengguna disebut percakapan dan direpresentasikan oleh objek MessagingStyle. Percakapan, atau MessagingStyle, berisi judul, pesan, dan apakah percakapan merupakan bagian dari grup pengguna.

Untuk memberi tahu pengguna tentang informasi baru dalam percakapan, seperti pesan baru, aplikasi akan memposting Notification ke sistem Android. Notification menggunakan objek MessagingStyle untuk menampilkan UI khusus pesan di menu notifikasi. Platform Android juga meneruskan Notification ini ke Android Auto, kemudian MessagingStyle diekstrak dan digunakan untuk memposting notifikasi melalui layar di mobil.

Android Auto juga mengharuskan aplikasi menambahkan objek Action ke Notification agar pengguna dapat membalas pesan dengan cepat atau menandainya sebagai telah dibaca, langsung dari menu notifikasi.

Singkatnya, satu percakapan direpresentasikan oleh objek Notification yang diberi gaya dengan objek MessagingStyle. MessagingStyle berisi semua pesan dalam percakapan tersebut di satu atau beberapa objek MessagingStyle.Message. Agar mematuhi Android Auto, aplikasi harus melampirkan objek Action balas dan tandai telah dibaca ke Notification.

Alur pesan

Bagian ini menjelaskan alur pesan standar antara aplikasi Anda dan Android Auto.

  1. Aplikasi Anda menerima pesan.
  2. Aplikasi membuat notifikasi MessagingStyle dengan objek Action balas dan tandai telah dibaca.
  3. Android Auto menerima peristiwa “notifikasi baru” dari sistem Android serta menemukan MessagingStyle, Action balas, dan Action tandai telah dibaca.
  4. Android Auto menghasilkan dan menampilkan notifikasi di mobil.
  5. Jika pengguna mengetuk notifikasi di layar mobil, Android Auto akan memicu Action tandai telah dibaca.
    • Di latar belakang, aplikasi Anda harus menangani peristiwa tandai sudah dibaca ini.
  6. Jika pengguna merespons notifikasi menggunakan suara, Android Auto akan menempatkan transkripsi respons pengguna ke dalam Action balas, lalu memicunya.
    • Di latar belakang, aplikasi Anda harus menangani peristiwa balas ini.

Asumsi awal

Halaman ini tidak memandu Anda dalam membuat seluruh aplikasi pesan. Contoh kode berikut menyertakan beberapa hal yang dibutuhkan aplikasi Anda sebelum Anda mulai mendukung pesan dengan 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)

Mendeklarasikan dukungan Android Auto

Saat menerima notifikasi dari aplikasi pesan, Android Auto akan memeriksa apakah aplikasi tersebut telah mendeklarasikan dukungan untuk Android Auto atau belum. Untuk memungkinkan dukungan ini, sertakan entri berikut dalam manifes aplikasi Anda:

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

Entri manifes ini merujuk ke file XML lain yang perlu Anda buat dengan jalur berikut: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml. Di automotive_app_desc.xml, deklarasikan kemampuan Android Auto yang didukung aplikasi Anda. Misalnya, guna mendeklarasikan dukungan untuk notifikasi, sertakan hal berikut:

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

Jika aplikasi Anda dapat ditetapkan sebagai pengendali SMS default, pastikan untuk menyertakan elemen <uses> berikut. Jika tidak, pengendali default bawaan Android Auto akan digunakan untuk menangani pesan SMS/MMS yang masuk saat aplikasi Anda ditetapkan sebagai pengendali SMS default, yang dapat menyebabkan notifikasi duplikat.

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

Mengimpor library inti AndroidX

Membuat notifikasi untuk digunakan dengan perangkat Auto memerlukan library inti AndroidX. Impor library ke project Anda sebagai berikut:

  1. Di file build.gradle tingkat atas, sertakan dependensi pada repositori Maven Google, seperti ditunjukkan pada contoh berikut:

Groovy

allprojects {
    repositories {
        google()
    }
}

Kotlin

allprojects {
    repositories {
        google()
    }
}
  1. Dalam file build.gradle modul aplikasi, sertakan dependensi library AndroidX Core, seperti ditunjukkan dalam contoh berikut:

Groovy

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

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

Kotlin

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

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

Menangani tindakan pengguna

Aplikasi pesan memerlukan cara untuk menangani pembaruan percakapan melalui Action. Untuk Android Auto, ada dua jenis objek Action yang perlu ditangani oleh aplikasi Anda: balas dan tandai sudah dibaca. Sebaiknya tangani menggunakan IntentService yang memberikan fleksibilitas untuk menangani panggilan yang berpotensi makin mahal di latar belakang sehingga membebaskan thread utama aplikasi Anda.

Menentukan tindakan intent

Tindakan Intent adalah string sederhana yang mengidentifikasi kegunaan Intent. Karena satu layanan dapat menangani beberapa jenis intent, akan lebih mudah untuk menentukan beberapa string tindakan daripada menentukan beberapa komponen IntentService.

Contoh aplikasi pesan dalam panduan ini memiliki dua jenis tindakan yang diperlukan: balas dan tandai telah dibaca, seperti ditunjukkan pada contoh kode berikut.

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

Membuat layanan

Untuk membuat layanan yang menangani objek Action ini, Anda memerlukan ID percakapan, yang merupakan struktur data arbitrer yang ditentukan oleh aplikasi Anda yang mengidentifikasi percakapan. Anda juga memerlukan kunci input jarak jauh, yang akan dibahas secara mendetail di bagian ini nanti. Contoh kode berikut membuat layanan untuk menangani tindakan yang diperlukan:

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

Untuk mengaitkan layanan ini dengan aplikasi Anda, Anda juga perlu mendaftarkan layanan dalam manifes aplikasi, seperti ditunjukkan dalam contoh berikut:

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

Membuat dan menangani intent

Tidak ada cara bagi aplikasi lain, termasuk Android Auto, untuk mendapatkan Intent yang memicu MessagingService, karena Intent diteruskan ke aplikasi lain melalui PendingIntent. Karena batasan ini, Anda perlu membuat objek RemoteInput agar aplikasi lain dapat mengembalikan teks balasan ke aplikasi Anda, seperti ditunjukkan dalam contoh berikut:

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

Dalam klausa tombol ACTION_REPLY dalam MessagingService, ekstrak informasi yang masuk ke Intent balas, seperti ditunjukkan dalam contoh berikut:

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

Anda menangani Intent tandai telah dibaca dengan cara yang sama. Namun, metode ini tidak memerlukan RemoteInput, seperti ditunjukkan dalam contoh berikut:

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

Klausa tombol ACTION_MARK_AS_READ dalam MessagingService tidak memerlukan logika lebih lanjut, seperti ditunjukkan dalam contoh berikut:

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

Memberitahukan pesan kepada pengguna

Setelah penanganan tindakan percakapan selesai, langkah berikutnya adalah membuat notifikasi yang mematuhi Android Auto.

Membuat tindakan

Objek Action dapat diteruskan ke aplikasi lain menggunakan Notification untuk memicu metode di aplikasi asli. Ini adalah cara Android Auto dapat menandai percakapan sebagai telah dibaca atau membalasnya.

Untuk membuat Action, mulailah dengan Intent. Contoh berikut menunjukkan cara membuat Intent "balas":

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

Selanjutnya, gabungkan Intent ini dalam PendingIntent yang menyiapkannya untuk penggunaan aplikasi eksternal. PendingIntent mengunci semua akses ke Intent yang digabungkan hanya dengan mengekspos sekumpulan metode tertentu yang memungkinkan aplikasi penerima mengaktifkan Intent atau mendapatkan nama paket aplikasi asal. Aplikasi eksternal tidak akan dapat mengakses Intent yang mendasarinya atau data di dalamnya.

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

Sebelum menyiapkan Action balas, perlu diketahui bahwa Android Auto memiliki tiga persyaratan untuk Action balas:

  • Tindakan semantik harus ditetapkan ke Action.SEMANTIC_ACTION_REPLY.
  • Action harus menunjukkan bahwa antarmuka pengguna tidak akan ditampilkan saat diaktifkan.
  • Action harus berisi satu RemoteInput.

Contoh kode berikut menyiapkan Action balas yang memenuhi persyaratan yang tercantum di atas:

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

Penanganan tindakan tandai sudah dibaca serupa, tetapi tidak ada RemoteInput. Oleh karena itu, Android Auto memiliki dua persyaratan untuk Action tandai sudah dibaca:

  • Tindakan semantik ditetapkan ke Action.SEMANTIC_ACTION_MARK_AS_READ.
  • Tindakan tersebut menunjukkan bahwa antarmuka pengguna tidak akan ditampilkan saat diaktifkan.

Contoh kode berikut menyiapkan Action tandai sudah dibaca yang memenuhi persyaratan ini:

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
}

Saat membuat intent yang tertunda, ada dua metode yang digunakan: createReplyId() dan createMarkAsReadId(). Metode ini berfungsi sebagai kode permintaan untuk setiap PendingIntent, yang digunakan oleh Android untuk mengontrol intent tertunda yang ada. Metode create() harus menampilkan ID unik untuk setiap percakapan, tetapi panggilan berulang untuk percakapan yang sama harus menampilkan ID unik yang sudah dibuat.

Pertimbangkan contoh dengan dua percakapan, A dan B: ID balasan Percakapan A adalah 100, dan ID tandai sudah dibaca adalah 101. ID balasan Percakapan B adalah 102, dan ID tandai sudah dibaca adalah 103. Jika percakapan A diupdate, ID balas dan tandai sudah dibaca tetap 100 dan 101. Untuk mengetahui informasi selengkapnya, lihat PendingIntent.FLAG_UPDATE_CURRENT.

Membuat MessagingStyle

MessagingStyle adalah operator informasi pesan yang digunakan oleh Android Auto untuk membaca setiap pesan dalam percakapan dengan keras.

Pertama, pengguna perangkat harus ditentukan dalam bentuk objek Person, seperti ditunjukkan dalam contoh berikut:

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

Lalu, Anda dapat membuat objek MessagingStyle dan menambahkan beberapa detail tentang percakapan.

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

Terakhir, tambahkan pesan yang belum dibaca.

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

Memaketkan dan mengirim notifikasi

Setelah membuat objek Action dan MessagingStyle, Anda dapat membuat dan memposting 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)
}

Referensi lainnya

Melaporkan masalah Fitur Pesan Android Auto

Jika mengalami masalah dengan pengembangan aplikasi pesan untuk Android Auto, Anda dapat melaporkannya menggunakan Issue Tracker Google. Pastikan untuk mengisi semua informasi yang diminta pada template masalah.

Laporkan masalah baru

Sebelum mengajukan masalah baru, periksa apakah masalah tersebut sudah dilaporkan dalam daftar masalah. Anda bisa berlangganan dan memberi suara pada masalah dengan mengklik bintang untuk masalah di tracker. Untuk mengetahui informasi selengkapnya, lihat Berlangganan pada topik Masalah.