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 untuk memperluas layanan mereka ke pengalaman berkendara melalui antarmuka pengguna standar yang memungkinkan pengemudi untuk terus mengawasi jalan.

Aplikasi yang mendukung pesan dapat menyediakan notifikasi pesannya yang akan memungkinkan Android Auto untuk menggunakannya 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 mendapatkan manfaat berupa notifikasi pesan yang dioptimalkan untuk semua perangkat Android, termasuk Android Auto. Pengoptimalan tersebut mencakup UI yang khusus untuk notifikasi pesan, animasi yang disempurnakan, dan dukungan untuk gambar inline.

Pelajaran ini mengasumsikan bahwa Anda sudah membuat aplikasi yang menampilkan pesan kepada pengguna dan menerima balasan pengguna, seperti aplikasi chat. Pelajaran ini menunjukkan cara memperluas aplikasi Anda untuk meneruskan pesan-pesan tersebut untuk ditampilkan dan dibalas di perangkat Auto.

Memulai

Agar dapat menyediakan layanan pesan untuk perangkat Auto, aplikasi Anda harus dapat melakukan berikut ini:

  1. Membuat dan mengirim objek NotificationCompat.MessagingStyle yang berisi objek Action reply dan mark-as-read.
  2. Menangani pengiriman balasan dan penandaan percakapan sebagai sudah dibaca dengan Service.
  3. Mengonfigurasi manifes untuk mengindikasikan bahwa aplikasi mendukung Android Auto.

Konsep dan objek

Sebelum mulai mendesain aplikasi, sebaiknya Anda memahami cara Android Auto menangani pesan.

Setiap penggalan komunikasi disebut pesan dan diwakili oleh class MessagingStyle.Message. Pesan menyertakan informasi pengirim, isi pesan, dan waktu pengiriman.

Komunikasi antarpengguna disebut percakapan dan diwakili oleh objek MessagingStyle. Setiap percakapan (atau MessagingStyle) berisi informasi judul, pesan, dan apakah percakapan tersebut melibatkan kelompok (ada lebih dari satu penerima) atau tidak.

Untuk memberi tahu pengguna tentang adanya pembaruan percakapan (misalnya pesan baru), aplikasi akan menampilkan Notification ke sistem Android. Notifikasi ini menggunakan objek MessagingStyle untuk menampilkan UI khusus pesan di menu notifikasi. Platform Android juga meneruskan Notification ini ke Android Auto, lalu MessagingStyle diekstrak dan digunakan untuk menampilkan notifikasi melalui layar pada mobil.

Aplikasi juga dapat menambahkan objek Action ke Notification untuk memungkinkan pengguna membalas pesan dengan cepat atau menandainya sebagai sudah dibaca langsung dari menu notifikasi. Android Auto memerlukan objek Action reply dan mark-as-read untuk mengelola percakapan.

Singkatnya, satu percakapan diwakili oleh satu objek Notification yang diberi gaya dengan satu objek MessagingStyle. MessagingStyle berisi semua pesan dalam percakapan tersebut dengan satu atau beberapa objek MessagingStyle.Message. Terakhir, agar sepenuhnya mematuhi persyaratan Android Auto, Anda harus menambahkan Action reply dan mark-as-read sebagai Notification.

Alur pesan

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

  1. Aplikasi Anda menerima pesan.
  2. Aplikasi Anda mengeluarkan notifikasi MessagingStyle dengan Action reply dan mark-as-read.
  3. Android Auto menerima peristiwa "notifikasi baru" dari sistem Android dan menemukan MessagingStyle, Action reply, dan Action mark-as-read.
  4. Android Auto menghasilkan dan menampilkan notifikasi di mobil.
  5. Jika pengguna mengetuk notifikasi pada layar mobil, Android Auto akan memicu Action mark-as-read.
    • Di latar belakang, aplikasi Anda harus menangani peristiwa mark-as-read ini.
  6. Jika pengguna merespons notifikasi melalui suara, Android Auto akan menyertakan transkripsi respons pengguna ke dalam Action reply, lalu memicunya.
    • Di latar belakang, aplikasi Anda harus menangani peristiwa reply ini.

Asumsi awal

Halaman ini tidak menunjukkan cara membuat aplikasi pesan secara keseluruhan. Namun, contoh kode di bawah mencakup beberapa hal yang harus sudah dimiliki 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 mengaktifkan dukungan ini, sertakan entri berikut ke 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 harus Anda buat dengan jalur berikut: YourAppProject/app/src/main/res/xml/automotive_app_desc.xml, di mana Anda mendeklarasikan kapabilitas Android Auto apa saja yang didukung oleh aplikasi Anda. Misalnya, untuk memasukkan dukungan untuk notifikasi, sertakan baris berikut dalam automotive_app_desc.xml Anda:

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

Jika aplikasi Anda memerlukan dukungan untuk menangani SMS, MMS, dan RCS, Anda juga harus menyertakan baris berikut:

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

Mengimpor library inti AndroidX

Pembuatan notifikasi yang akan digunakan dengan perangkat Auto memerlukan library inti AndroidX, yang dapat diimpor ke project Anda sebagai berikut:

  1. Dalam file build.gradle level teratas, pastikan Anda menyertakan repositori Maven Google, seperti berikut:

    allprojects {
            repositories {
                google()
            }
        }
        
  2. Dalam file build.gradle modul aplikasi Anda, sertakan dependensi library inti AndroidX, seperti berikut:

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

Menangani tindakan pengguna

Aplikasi pesan Anda memerlukan cara untuk menangani pembaruan percakapan melalui Action. Untuk Android Auto, ada dua jenis objek Action yang perlu ditangani aplikasi Anda: reply dan mark-as-read. Cara yang direkomendasikan untuk menanganinya adalah melalui penggunaan IntentService. IntentService memberikan fleksibilitas untuk menangani panggilan yang berpotensi mahal di latar belakang dan membebaskan thread utama aplikasi.

Menentukan tindakan intent

Tindakan Intent (jangan disamakan dengan tindakan notifikasi) adalah string sederhana yang mengidentifikasi kegunaan Intent. Karena satu layanan dapat menangani beberapa jenis intent, akan lebih mudah untuk menentukan beberapa string Intent.action daripada menentukan beberapa IntentServices.

Dalam contoh aplikasi pesan ini, kita memiliki dua jenis tindakan: reply dan mark-as-read, sebagaimana dideklarasikan dalam 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 Action ini, Anda memerlukan ID percakapan, yaitu struktur data arbitrer yang ditentukan oleh aplikasi Anda untuk mengidentifikasi percakapan, dan kunci input jarak jauh, yang akan dibahas lebih terperinci nanti di bagian ini. Contoh kode berikut membuat layanan seperti itu.

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

Untuk mengaitkan layanan ini dengan aplikasi Anda, Anda juga perlu mendaftarkan layanan tersebut dalam manifes aplikasi Anda, seperti ditunjukkan di bawah ini.

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

Membuat dan menangani Intent

Tidak ada cara bagi aplikasi lain untuk mendapatkan Intent yang memicu MessagingService karena setiap Intent diteruskan ke aplikasi eksternal melalui PendingIntent. Karena keterbatasan ini, Anda perlu membuat objek RemoteInput yang akan memungkinkan aplikasi lain memberikan teks "reply" ke aplikasi Anda, seperti ditunjukkan di bawah ini.

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

Setelah Anda mengetahui apa yang masuk ke Intent reply, tangani TODO untuk klausa switch ACTION_REPLY dalam MessagingService dan ekstrak informasi tersebut, seperti ditunjukkan di bawah ini:

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

Penanganan Intent mark-as-read sama dengan di atas (tetapi tidak memerlukan 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
    }
    

Contoh kode di bawah ini menangani TODO untuk klausa switch ACTION_MARK_AS_READ dalam MessagingService:

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

Memberitahukan pesan kepada pengguna

Setelah penanganan tindakan percakapan aplikasi pesan selesai, kita dapat beralih ke membuat notifikasi yang sesuai dengan Android Auto.

Membuat tindakan

Action adalah objek yang dapat diteruskan ke aplikasi lain melalui Notification untuk memicu metode pada aplikasi asli. Seperti itulah cara Android Auto menandai percakapan sebagai sudah dibaca, dan membalasnya.

Untuk membuat Action, mulailah dengan Intent, seperti ditunjukkan dengan Intent "reply" di bawah:

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

Selanjutnya, kita menggabung Intent ini dalam PendingIntent yang menyiapkannya untuk penggunaan aplikasi eksternal. PendingIntent mengunci semua akses ke Intent gabungan dengan hanya menampakkan sekumpulan metode tertentu yang memungkinkan aplikasi penerima untuk mengaktifkan Intent atau mendapatkan nama paket aplikasi asal; tetapi tidak mengizinkan aplikasi eksternal untuk mengakses Intent pokok atau data di dalamnya.

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

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

  • Tindakan semantik harus ditetapkan ke Action.SEMANTIC_ACTION_REPLY.
  • Action harus mengindikasikan bahwa ia tidak akan menampilkan antarmuka pengguna saat diaktifkan.
  • Action harus berisi satu RemoteInput.

Contoh kode berikut menyiapkan Action reply sekaligus menangani persyaratan yang tercantum di atas:

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

Di sisi mark-as-read, kita melakukan hal yang sama kecuali bahwa RemoteInput tidak ada. Dengan demikian, Android Auto memiliki 2 persyaratan untuk Action mark-as-read:

  • Tindakan semantik ditetapkan ke Action.SEMANTIC_ACTION_MARK_AS_READ.
  • Tindakan ini mengindikasikan bahwa ia tidak akan menampilkan antarmuka pengguna saat diaktifkan.
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
    }
    

Membuat MessagingStyle

MessagingStyle adalah pembawa informasi pesan dan itulah yang digunakan Android Auto untuk membacakan setiap pesan dalam percakapan. Pertama, pengguna perangkat harus ditentukan dalam bentuk objek 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()
        // ...
    

Selanjutnya, Anda dapat membuat objek MessagingStyle dan memberikan beberapa detail tentang percakapan.

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

Terakhir, tambahkan pesan yang belum dibaca.

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

Memaketkan dan mengirim notifikasi

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

Lihat juga