Dokumen ini menunjukkan cara mengintegrasikan dan menyesuaikan Headless Mobile SDK di aplikasi Android Anda. Bagian ini mencakup tindakan smart, lampiran, dan pengalihan.
Mengonfigurasi tindakan dan lampiran smart
Tindakan smart memungkinkan agen dan konsumen bertukar informasi terstruktur yang kaya, seperti foto, video, cuplikan teks, dan verifikasi biometrik.
Dalam arsitektur tanpa antarmuka, SDK bertindak secara ketat sebagai lapisan transportasi yang aman. Hal ini memberi tahu Anda kapan tindakan diperlukan atau diminta, dan secara aman mentransfer data ke platform CCAI. Karena Anda memiliki antarmuka pengguna, aplikasi Anda sepenuhnya bertanggung jawab untuk membuat layar kamera, pemilih foto, kotak teks, dan memicu perintah biometrik bawaan Android.
Tindakan cerdas pra-sesi
Tindakan pra-sesi mengumpulkan konteks (seperti nomor akun atau video produk yang rusak) sebelum pengguna bergabung ke antrean, sehingga mengurangi waktu penanganan agen secara signifikan.
Yang disediakan SDK: Aturan dan persyaratan untuk antrean tertentu (misalnya, apakah verifikasi biometrik, upload foto, atau input teks bersifat opsional atau wajib sebelum bergabung).
Yang Anda bangun: Layar UI pra-sesi (kamera, input teks, dan perintah biometrik) yang muncul antara pengguna mengetuk antrean dan memasuki ruang tunggu, serta logika penyiapan cache disk untuk menyimpan media dengan aman hingga sesi terhubung.
Contoh penerapan
Kode ini memeriksa konfigurasi SDK untuk melihat apakah antrean yang dipilih memiliki persyaratan pra-sesi. Contoh ini menunjukkan cara menangani persyaratan video dengan menyimpan referensi file Uri di disk, bukan menyimpan video dalam memori.
// 1. Check requirements and collect data BEFORE joining the queue
fun checkPreSessionRequirements(queueMenu: QueueMenu) {
val smartActions = queueMenu.settings
if (smartActions.isNullOrEmpty()) {
startChat(queueMenu)
return
}
// Example: handle a video requirement
presentVideoPicker { localFileUri ->
// IMPORTANT: localFileUri should point to context.cacheDir
MySessionStateManager.pendingPreSessionVideo = localFileUri
startChat(queueMenu)
}
}
// 2. Upload and clean up AFTER the session officially connects
fun handleSessionConnected() {
val pendingVideoUri = MySessionStateManager.pendingPreSessionVideo ?: return
// Upload using the standard in-session pipeline
// then clean up the temporary file
val file = File(pendingVideoUri.path ?: return)
file.delete()
MySessionStateManager.pendingPreSessionVideo = null
}
Membaca konfigurasi tindakan smart pra-sesi
SDK mengekspos konfigurasi tindakan smart pra-sesi melalui dua jalur pelengkap. Periksa keduanya untuk menentukan apakah pengambilan pra-sesi diperlukan untuk antrean dan saluran tertentu.
Jalur A - Tingkat antrean (QueueMenu.settings[].sdk.preSessionSmartAction)
// `QueueMenuSetting` and `QueueMenuSDKSetting` (simplified)
data class QueueMenuSetting(
val sdk: QueueMenuSDKSetting?
// ... other fields ...
)
data class QueueMenuSDKSetting(
val preSessionSmartAction: Boolean?
// ... other fields ...
)
// Usage - "does any queue-level setting require a pre-session smart action?"
fun queueRequiresPreSession(menu: QueueMenu): Boolean {
return menu.settings?.any { it.sdk?.preSessionSmartAction == true } ?: false
}
Jalur B - Tingkat channel (VoiceCallChannel.preSessionSmartAction)
// Usage - "does the voice channel specifically require pre-session capture?"
val requiresPreSessionOnVoice =
menu.channels.voiceCall?.preSessionSmartAction == true
Gunakan Jalur A untuk memutuskan apakah akan menampilkan penghalang pra-sesi sebelum pengguna memilih channel. Gunakan Jalur B untuk penyempurnaan khusus saluran setelah saluran dipilih (misalnya, hanya meminta sebelum panggilan suara instan).
Tindakan cerdas dalam sesi (diprakarsai agen)
Selama panggilan suara atau chat aktif, agen dapat mengklik tombol di dasbornya untuk meminta data dari pengguna (misalnya, Kirim foto tanda pengenal Anda atau Lakukan autentikasi dengan biometrik).
Yang disediakan SDK: Peristiwa real-time yang memberi tahu aplikasi Anda bahwa agen telah meminta jenis tindakan smart tertentu.
Yang Anda buat: Pemberitahuan dalam chat atau dalam panggilan yang memberi tahu pengguna tentang permintaan, meluncurkan alat bawaan yang sesuai (kamera, galeri foto, kolom teks, atau BiometricPrompt Android), dan meneruskan hasilnya kembali ke SDK.
Contoh penerapan
Di sini, aplikasi Anda memproses peristiwa SDK. Saat mendeteksi permintaan agen, alat ini akan menampilkan peringatan, lalu membuka alat Android yang sesuai.
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
// Listen for Smart Action requests from the active SDK session.
//
// `SmartActionRequest` exposes a nested `smartAction: SmartAction`, and the
// switchable type lives at `request.smartAction.type`. The `SmartActionType`
// enum is `PascalCase: Unknown`, `Verification`, `Screenshot`, `Photo`, `Video`,
// `TextInput`, `ScreenShare`. Use `Verification` for Face ID / fingerprint flows.
fun handleSmartActionRequest(request: SmartActionRequest) {
when (request.smartAction.type) {
SmartActionType.Photo -> {
promptUserForPhoto(message = "The agent requested a photo.") { capturedBitmap ->
// Send the image back to the agent using the SDK.
sendAttachment(image = capturedBitmap)
}
}
SmartActionType.Verification -> {
val executor = ContextCompat.getMainExecutor(this)
val biometricPrompt = BiometricPrompt(this, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
sendBiometricResult(success = true)
}
override fun onAuthenticationFailed() {
sendBiometricResult(success = false)
}
override fun onAuthenticationError(
errorCode: Int, errString: CharSequence
) {
sendBiometricResult(success = false)
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Identity Verification")
.setSubtitle("Verify your identity to continue")
.setNegativeBtnText("Cancel")
.build()
biometricPrompt.authenticate(promptInfo)
}
SmartActionType.TextInput -> {
presentTextInputPrompt(message = "The agent requested text input.") { text ->
sendSmartActionText(text)
}
}
SmartActionType.Video -> {
// Agent-requested video capture. Reuse the same disk-cached file
// pattern recommended for pre-session media keep the recording on
// disk and pass the local Uri to the SDK rather than holding a
// large `ByteArray` in memory.
promptUserForVideo(message = "The agent requested a video.") { capturedVideoUri ->
sendAttachment(videoUri = capturedVideoUri)
}
}
SmartActionType.Screenshot -> {
// The SDK exposes `Screenshot` as a distinct request type the
// user captures (or selects), a single still image of the current
// screen. This is **not** the same as `ScreenShare`, which
// starts a live co-browse session.
promptUserForScreenshot(message = "The agent requested a screenshot.") { capturedBitmap ->
sendAttachment(image = capturedBitmap)
}
}
SmartActionType.ScreenShare -> {
// Hand off to the full screen share flow (see Headless Mobile SDK for Android: Voice, screen share, scheduled call, and email).
// Don't confuse with `Screenshot`; `ScreenShare` starts a
// live co-browse session using `CCAIScreenShare`, while `Screenshot`
// captures a single still frame.
startScreenShareFlow()
}
SmartActionType.Unknown -> {
// Future-proof: a smart action type the SDK does not recognize.
// Log and continue; don't block the user.
Log.w("CCAI", "Received unknown smart action type")
}
}
}
Tindakan smart yang dimulai agen tiba melalui peristiwa SDK sesi aktif. Berlangganan ke peristiwa panggilan atau chat yang relevan sebelum memulai sesi agar aplikasi Anda dapat menampilkan perintah yang sesuai untuk pelanggan segera setelah permintaan tiba.
Memberi tahu dan melacak tindakan smart
Gunakan smartActionService untuk mengonfirmasi dan memperbarui tindakan smart saat pengguna
melakukan tindakan tersebut.
val router = CommunicationRouter(
id = chatId,
type = CommunicationType.Chat
)
// Notify the server that a smart action has been received.
lifecycleScope.launch {
try {
CCAI.smartActionService?.notifySmartActionReceived(
router = router,
smartActionId = smartActionId
)
Log.d("CCAI", "Smart action notification sent successfully")
} catch (e: Exception) {
Log.e("CCAI", "Failed to notify smart action: ${e.message}")
}
}
// Update the smart action status as the user completes it.
lifecycleScope.launch {
try {
CCAI.smartActionService?.updateSmartActionStatus(
router = router,
smartActionId = smartActionId,
status = SmartActionStatus.Finished
)
Log.d("CCAI", "Smart action status updated successfully")
} catch (e: Exception) {
Log.e("CCAI", "Failed to update smart action status: ${e.message}")
}
}
Jika tindakan meminta input teks, kumpulkan teks di UI Anda dan kirimkan melalui layanan tindakan smart sebelum menandai tindakan sebagai selesai.
Formulir web
Yang disediakan SDK: API untuk mengambil, menampilkan, dan memvalidasi formulir web yang dipicu oleh agen selama sesi chat.
Yang Anda buat: UI rendering formulir web dan alur pengiriman yang mengemas respons pengguna.
Contoh penerapan
Kode ini mengambil formulir web berdasarkan ID eksternal dan ID tindakan smart-nya, lalu memvalidasi respons sebelum pengiriman.
// Fetch a web form - `fetchWebForm` takes two positional parameters on Android.
lifecycleScope.launch {
try {
val webFormResponse = CCAI.chatService?.fetchWebForm(
externalFormId = "form123",
smartActionId = 123
)
if (webFormResponse != null) {
Log.d("CCAI", "Web form received: $webFormResponse")
// Render the web form in your custom UI.
renderWebForm(webFormResponse)
}
} catch (e: Exception) {
Log.e("CCAI", "Failed to fetch web form: ${e.message}")
}
}
// Validate a web form response.
lifecycleScope.launch {
try {
val isValid = CCAI.chatService?.validateWebForm(webFormResponse)
if (isValid == true) {
Log.d("CCAI", "Web form is valid")
} else {
Log.d("CCAI", "Web form validation failed")
}
} catch (e: Exception) {
Log.e("CCAI", "Failed to validate web form: ${e.message}")
}
}
Formulir kustom
Yang disediakan SDK: API untuk mengambil definisi formulir yang dapat dikonfigurasi (dengan beberapa jenis pertanyaan) dan mengirimkan jawaban pengguna.
Yang akan Anda bangun: UI formulir dinamis yang merender setiap jenis pertanyaan (kolom teks, dropdown, dll.), memvalidasi input, dan mengumpulkan respons untuk pengiriman.
Contoh penerapan
Kode ini mengambil definisi pertanyaan formulir kustom, lalu mengemas dan mengirimkan jawaban pengguna.
// Retrieve custom form details
lifecycleScope.launch {
try {
val formDetails = CCAI.chatService?.getCustomFormDetails(formId = 123)
if (formDetails != null) {
Log.d("CCAI", "Form Title: ${formDetails.title ?: "No title"}")
Log.d("CCAI", "Questions: ${formDetails.questions.size}")
// Render the form dynamically based on question types
renderCustomForm(formDetails)
}
} catch (e: Exception) {
Log.e("CCAI", "Failed to get custom form details: ${e.message}")
}
}
// Submit a custom form
val request = SubmitCustomFormRequest(
smartActionId = 123,
formResponse = listOf(
SubmitCustomFormRequest.Answer(questionId = 1, value = "John Doe"),
SubmitCustomFormRequest.Answer(questionId = 2, value = "john@example.com")
)
)
lifecycleScope.launch {
try {
CCAI.chatService?.submitCustomForm(request = request)
Log.d("CCAI", "Form submitted successfully")
} catch (e: Exception) {
Log.e("CCAI", "Failed to submit form: ${e.message}")
}
}
Untuk definisi enum CustomFormDetailsResponse, CustomFormQuestion,
CustomFormQuestionOption, dan CustomFormQuestionType lengkap, lihat
Headless Mobile SDK — Referensi API Android.
Lampiran yang dimulai konsumen (chat)
Pengguna tidak perlu menunggu agen bertanya; mereka dapat secara proaktif mengirim gambar dan video selama percakapan. SDK menerapkan batas ukuran file yang ketat dan secara otomatis mencoba kompresi sebelum transmisi.
Batas ukuran lampiran
Gambar: ≤ 2 MB
Video: ≤ 20 MB
Yang disediakan SDK: Pipeline upload aman yang menangani transmisi jaringan dan otomatis mengompresi file untuk memenuhi batas Platform CCAI.
Yang Anda bangun: Tombol klip kertas / lampiran di UI chat Anda, pemilih foto/video, dan balon chat visual yang menampilkan status upload (memuat, terkirim, atau gagal).
Contoh penerapan
Saat pengguna memilih foto atau video dari galeri mereka, teruskan file lokal
Uri ke SDK menggunakan sendMessage API yang sama yang digunakan untuk semua pesan
keluar. Untuk mengetahui informasi selengkapnya, lihat Mengirim
pesan.
SDK menangani kompresi dan upload.
// Called when the user picks an image from their photo gallery
fun userDidSelectImage(imageUri: Uri) {
val messageBubble = myChatAdapter.addLoadingImageBubble()
lifecycleScope.launch {
try {
CCAI.chatService?.sendMessage(
OutgoingMessageContent.Photos(
images = listOf(imageUri),
smartAction = null
)
)
messageBubble.markAsSent()
} catch (e: Exception) {
messageBubble.showError("Failed to send: ${e.message}")
}
}
}
Lampiran yang dimulai agen (chat)
Agen dapat mengirim file kembali kepada pengguna, termasuk gambar, video, klip audio, dan dokumen (PDF, CSV, dll.).
Jenis file yang didukung
Platform CCAI Platform menerima 19 jenis file dalam empat kategori. UI kustom Anda harus menangani setiap kategori dengan baik (pratinjau gambar, pemutar video, pemutar audio, download dokumen).
| Kategori | Jenis yang didukung |
|---|---|
| Gambar | JPEG, PNG, GIF, dan WebP. |
| Video | MP4, MOV, AVI, WMV, dan WebM. |
| Audio | MP3, WAV, M4A, dan WEBA. |
| Dokumen | PDF, DOC, XLS, PPT, CSV, dan TXT. |
Yang disediakan SDK: Payload pesan chat masuk yang dikirim menggunakan Flow pesan layanan chat yang menyertakan metadata lampiran — lihat Headless Mobile SDK for Android: Topik lanjutan, pelokalan, dan alur pasca-sesi. Layanan chat juga menyediakan downloadMedia
untuk mengambil media yang dikirim agen secara aman.
Yang Anda bangun: UI untuk merender lampiran ini di feed chat. Aplikasi Anda memutuskan apakah akan menampilkan pratinjau gambar, thumbnail video, kontrol pemutaran audio, atau tombol download dokumen.
Contoh penerapan
Amati pesan masuk menggunakan
Flow pesan layanan chat. Saat pesan baru menyertakan metadata media, panggil downloadMedia
sebelum merender atau membuka file.
downloadMedia menampilkan DownloadMediaResponse { data: ByteArray, fileName:
String? } — byte yang didownload sepenuhnya ditambah nama file tampilan opsional. SDK tidak menyimpan download dalam cache. Jika UI Anda men-scroll kembali ke balon lama atau
memasuki kembali tampilan chat, aplikasi Anda bertanggung jawab untuk meng-cache gambar yang didekode
atau mempertahankan byte; memanggil downloadMedia lagi akan memicu pengambilan
jaringan lain.
import android.graphics.BitmapFactory
import java.io.File
lifecycleScope.launch {
CCAI.chatService?.messagesReceived?.collect { messages ->
for (message in messages) {
renderIncomingMessage(message)
}
}
}
fun renderIncomingMessage(message: ChatMessage) {
val media = message.media
if (media == null) {
myChatAdapter.insertTextBubble(text = message.text)
return
}
lifecycleScope.launch {
try {
// `downloadMedia` returns the raw bytes plus an optional filename.
val response = CCAI.chatService?.downloadMedia(
mediaId = media.id,
mediaType = media.type
) ?: return@launch
when (media.type) {
DownloadableMediaType.Photo -> {
// Decode in-memory; no disk write needed.
val bitmap = BitmapFactory.decodeByteArray(
response.data, 0, response.data.size
)
myChatAdapter.insertImageBubble(
bitmap = bitmap,
fileName = response.fileName
)
}
DownloadableMediaType.Video,
DownloadableMediaType.Audio,
DownloadableMediaType.Document -> {
// Larger media usually needs an on-disk file for a player
// or document viewer. Write to the app's cache directory
// yourself and hand the resulting File/Uri to your UI.
val file = File(
applicationContext.cacheDir,
response.fileName ?: "attachment"
)
file.writeBytes(response.data)
myChatAdapter.insertAttachmentBubble(
file = file,
mediaType = media.type
)
}
}
} catch (e: Exception) {
myChatAdapter.insertAttachmentErrorBubble(fileName = message.media?.name)
}
}
}
Pengalihan (di luar jam kerja dan kelebihan kapasitas)
Pengalihan mengalihkan pengguna saat tim dukungan Anda tidak tersedia. Hal ini biasanya terjadi dalam dua skenario:
Di luar jam kerja: pengguna mencoba menghubungi dukungan di luar jam operasional yang dikonfigurasi.
Kelebihan kapasitas: antrean terbuka, tetapi waktu tunggu melebihi batas yang dikonfigurasi admin.
Dalam arsitektur headless, SDK tidak otomatis memblokir pengguna atau memunculkan layar error. Sebagai gantinya, aplikasi ini secara diam-diam meneruskan status pengalihan dan opsi penggantian yang dikonfigurasi admin ke aplikasi Anda. Aplikasi Anda harus memeriksa status ini sebelum mencoba memulai sesi.
Menangani status pengalihan
Yang disediakan SDK: deflectionFrom: List<QueueMenuDeflection>? pada
QueueMenu yang mencantumkan aturan pengalihan masuk yang merutekan antrean lain
ke antrean ini. Setiap QueueMenuDeflection hanya memiliki dua kolom:
deflectionType: DeflectionType?: kategori pengalihan (misalnya, di luar jam kerja atau kelebihan kapasitas).menuId: Int?: ID antrean sumber yang dialihkan ke antrean ini.
QueueMenuDeflection tidak membawa pesan tampilan atau label yang dapat dibaca manusia. Untuk salinan yang ditampilkan kepada pengguna (misalnya, Kantor kami tutup),
gunakan afterHoursMessage antrean (berasal dari
QueueMenu.settings[].afterHours) atau string yang dikonfigurasi admin Anda sendiri. Untuk nama tampilan antrean tujuan yang telah diselesaikan, cari dari hierarki menu yang sudah diambil dengan menuId.
Yang Anda bangun: Logika untuk memeriksa status pengalihan ini sebelum menempatkan pengguna dalam antrean, dan UI kustom (seperti pemberitahuan layar penuh atau banner) yang menampilkan pesan yang sesuai dan tombol perutean alternatif.
Contoh penerapan
Kode berikut mencegat upaya pengguna untuk memulai chat. Hal ini memeriksa metadata pengalihan SDK di QueueMenu dan, jika
aturan aktif, menampilkan layar pengalihan menggunakan
afterHoursMessage antrean (atau penggantian).
fun onSupportMenuTapped(queueMenu: QueueMenu) {
// 1. Check if any deflection rules route into this queue.
val deflections = queueMenu.deflectionFrom
if (!deflections.isNullOrEmpty()) {
// Deflection is active - stop routing. Show the custom deflection screen instead.
val message = queueMenu.afterHoursMessage?.text
?: "Support is currently unavailable."
showDeflectionScreen(
message = message,
deflections = deflections
)
} else {
// No deflection rules apply. Safe to proceed to channel selection or directly to chat/call.
startSupportFlow(queueMenu)
}
}
Opsi pembelokan render
Saat aturan pengalihan aktif, admin Anda kemungkinan akan mengonfigurasi cara alternatif bagi pengguna untuk mendapatkan bantuan — biasanya dengan mengarahkan ke antrean lain (yang dapat Anda selesaikan menggunakan menuId) atau dengan menampilkan afterHoursMessage yang dikonfigurasi di antrean saat ini.
Yang disediakan SDK: Daftar entri QueueMenuDeflection di
deflectionFrom, masing-masing dengan deflectionType dan menuId.
Yang Anda bangun: Menu dinamis yang mengulang entri ini, menyelesaikan
setiap menuId terhadap struktur menu yang sudah diambil untuk mendapatkan nama tampilan, dan
membuat tombol yang sesuai.
Contoh penerapan
Kode ini mengambil entri pembelokan dan
membangun layar secara dinamis. Tombol ini menggunakan nama antrean sumber (diperoleh dari
menuId) sebagai label tombol, dan kembali ke deflectionType jika nama
tidak tersedia.
fun showDeflectionScreen(message: String, deflections: List<QueueMenuDeflection>) {
// 1. Update your UI with the admin's configured message (or your fallback).
deflectionTitleTextView.text = message
// 2. Loop through the incoming deflection rules and generate buttons.
for (deflection in deflections) {
val sourceMenu = deflection.menuId?.let { findMenuById(it) }
val label = sourceMenu?.name
?: deflection.deflectionType?.name
?: "Alternative option"
val button = MaterialButton(this).apply {
text = label
setOnClickListener {
handleDeflectionAction(deflection)
}
}
menuLinearLayout.addView(button)
}
// 3. Present the compiled screen to the user.
deflectionContainer.isVisible = true
}
// Walk your previously fetched menu tree (for example, by using `QueueMenu.flatten()`) to resolve a `menuId`.
private fun findMenuById(id: Int): QueueMenu? =
cachedRootMenus.flatMap { it.flatten() }.firstOrNull { it.id == id }
deflectionFrom adalah daftar aturan pengalihan yang dialihkan ke menu ini —
menuId setiap entri mengidentifikasi antrean sumber yang dialihkan ke
antrean saat ini. Aplikasi Anda akan memutuskan cara mengeksposnya. Beberapa integrasi menampilkan
tujuan antrean sebagai di sinilah kami akan mengirimkan Anda, sementara yang lain
hanya menggunakan keberadaan aturan apa pun sebagai gerbang untuk menampilkan layar coba lagi nanti generik.