Questo documento mostra come integrare e personalizzare l'SDK mobile headless nella tua app per Android. Include le azioni rapide, gli allegati e la deviazione.
Configurare le azioni intelligenti e gli allegati
Le azioni intelligenti consentono ad agenti e consumatori di scambiarsi informazioni ricche e strutturate come foto, video, snippet di testo e verifica biometrica.
Nell'architettura headless, l'SDK funge esclusivamente da livello di trasporto sicuro. Ti comunica quando è richiesta o richiesta un'azione e trasporta in modo sicuro i dati alla piattaforma CCAI Platform. Poiché l'interfaccia utente è di tua proprietà, la tua app è completamente responsabile della creazione delle schermate della fotocamera, dei selettori di foto, delle caselle di testo e dell'attivazione dei prompt biometrici integrati di Android.
Azioni rapide pre-sessione
Le azioni pre-sessione raccolgono il contesto (ad esempio un numero di conto bancario o un video di un prodotto rotto) prima che l'utente entri in coda, riducendo significativamente il tempo di gestione dell'agente.
Cosa fornisce l'SDK: le regole e i requisiti per una coda specifica (ad esempio, se la verifica biometrica, il caricamento di foto o l'inserimento di testo sono facoltativi o obbligatori prima di partecipare).
Cosa crei: le schermate dell'interfaccia utente pre-sessione (fotocamera, inserimento di testo e richieste biometriche) che vengono visualizzate tra il momento in cui l'utente tocca una coda e quello in cui entra nella sala d'attesa, nonché la logica di memorizzazione nella cache su disco per conservare in modo sicuro i contenuti multimediali fino a quando la sessione non si connette.
Esempio di implementazione
Questo codice controlla la configurazione dell'SDK per verificare se
la coda scelta ha requisiti pre-sessione. Mostra come gestire un requisito video mantenendo un riferimento al file Uri sul disco anziché memorizzare il video.
// 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
}
Lettura della configurazione dell'azione intelligente pre-sessione
L'SDK espone la configurazione delle azioni intelligenti pre-sessione tramite due percorsi complementari. Controlla entrambi per determinare se l'acquisizione pre-sessione è necessaria per una determinata coda e un determinato canale.
Percorso A - Livello coda (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
}
Percorso B - A livello di canale (VoiceCallChannel.preSessionSmartAction)
// Usage - "does the voice channel specifically require pre-session capture?"
val requiresPreSessionOnVoice =
menu.channels.voiceCall?.preSessionSmartAction == true
Utilizza Percorso A per decidere se mostrare un gate pre-sessione prima che l'utente scelga un canale. Utilizza il percorso B per perfezionamenti specifici per canale dopo la selezione di un canale (ad esempio, la richiesta solo prima di una chiamata vocale istantanea).
Azioni rapide in sessione (avviate dall'agente)
Durante una chat o una chiamata vocale attiva, un agente può fare clic su un pulsante della dashboard per richiedere dati all'utente (ad esempio, Invia una foto del tuo documento di identità o Autenticati con la biometria).
Cosa fornisce l'SDK: eventi in tempo reale che avvisano l'app che l'agente ha richiesto un tipo specifico di azione intelligente.
Cosa crei: un avviso in chat o in chiamata che notifica all'utente la
richiesta, avvia lo strumento integrato appropriato (fotocamera, raccolta fotografica, campo
di testo o Android BiometricPrompt) e restituisce il risultato all'SDK.
Esempio di implementazione
Qui, la tua app ascolta gli eventi SDK. Quando rileva una richiesta dell'agente, mostra un avviso e poi apre lo strumento Android appropriato.
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")
}
}
}
Le azioni intelligenti avviate dall'agente arrivano tramite gli eventi SDK della sessione attiva. Iscriviti agli eventi di chat o chiamata pertinenti prima di iniziare la sessione in modo che la tua app possa presentare il prompt appropriato rivolto ai clienti non appena arriva una richiesta.
Notificare e monitorare le azioni rapide
Utilizza smartActionService per confermare e aggiornare le azioni intelligenti man mano che l'utente
le completa.
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}")
}
}
Se l'azione richiede l'inserimento di testo, raccogli il testo nella tua UI e invialo tramite il servizio di azioni intelligenti prima di contrassegnare l'azione come completata.
Moduli web
Cosa fornisce l'SDK: API per recuperare, visualizzare e convalidare i moduli web attivati dagli agenti durante una sessione di chat.
Cosa crei: la UI di rendering del modulo web e il flusso di invio che raggruppa le risposte dell'utente.
Esempio di implementazione
Questo codice recupera un modulo web in base al suo ID esterno e all'ID azione intelligente, quindi convalida la risposta prima dell'invio.
// 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}")
}
}
Moduli personalizzati
Cosa fornisce l'SDK: API per recuperare definizioni di moduli configurabili (con più tipi di domande) e inviare le risposte dell'utente.
Cosa crei: un'interfaccia utente del modulo dinamica che esegue il rendering di ogni tipo di domanda (campi di testo, menu a discesa e così via), convalida l'input e raccoglie le risposte per l'invio.
Esempio di implementazione
Questo codice recupera le definizioni delle domande di un modulo personalizzato, quindi impacchetta e invia le risposte dell'utente.
// 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}")
}
}
Per le definizioni complete delle enumerazioni CustomFormDetailsResponse, CustomFormQuestion,
CustomFormQuestionOption e CustomFormQuestionType, consulta
il documento Headless Mobile SDK — Android API Reference.
Allegati avviati dai consumatori (chat)
Gli utenti non devono aspettare che un agente lo chieda, ma possono inviare in modo proattivo immagini e video durante una chat. L'SDK applica limiti rigorosi alle dimensioni dei file e tenta automaticamente la compressione prima della trasmissione.
Limiti di dimensione degli allegati
Immagini: ≤ 2 MB
Video: ≤ 20 MB
Cosa fornisce l'SDK: una pipeline di caricamento sicura che gestisce la trasmissione di rete e comprime automaticamente i file per rispettare i limiti di CCAI Platform.
Cosa crei: il pulsante a forma di graffetta / allegato nell'interfaccia utente della chat, il selettore di foto/video e la bolla di chat visiva che mostra lo stato del caricamento (caricamento, invio o errore).
Esempio di implementazione
Quando un utente seleziona una foto o un video dalla galleria, passa il file locale
Uri all'SDK utilizzando la stessa API sendMessage utilizzata per tutti i messaggi
in uscita. Per saperne di più, consulta Invio di
messaggi.
L'SDK gestisce la compressione e il caricamento.
// 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}")
}
}
}
Allegati avviati dall'agente (chat)
Gli agenti possono inviare file all'utente, tra cui immagini, video, clip audio e documenti (PDF, CSV e così via).
Tipi di file supportati
La piattaforma CCAI accetta 19 tipi di file in quattro categorie. La tua UI personalizzata deve gestire ogni categoria in modo appropriato (anteprima immagine, video player, audio player, download di documenti).
| Categoria | Tipi supportati |
|---|---|
| Immagini | JPEG, PNG, GIF e WebP. |
| Video | MP4, MOV, AVI, WMV e WebM. |
| Audio | MP3, WAV, M4A e WEBA. |
| Documenti | PDF, DOC, XLS, PPT, CSV e TXT. |
Cosa fornisce l'SDK: payload dei messaggi di chat in arrivo forniti utilizzando il messaggio Flow del servizio di chat che include i metadati degli allegati. Consulta SDK Headless Mobile per Android: argomenti avanzati, localizzazione e flussi post-sessione. Il servizio di chat fornisce anche downloadMedia
per recuperare in modo sicuro i contenuti multimediali inviati dall'agente.
Cosa crei: la UI per il rendering di questi allegati nel feed della chat. La tua app decide se mostrare anteprime di immagini, miniature di video, controlli di riproduzione audio o pulsanti di download di documenti.
Esempio di implementazione
Osserva i messaggi in arrivo utilizzando il Flow dei messaggi del servizio di chat. Quando un nuovo messaggio include metadati multimediali, chiama downloadMedia
prima di eseguire il rendering o aprire il file.
downloadMedia restituisce DownloadMediaResponse { data: ByteArray, fileName:
String? }: i byte scaricati completamente più un nome file visualizzato facoltativo. L'SDK non memorizza nella cache i download. Se la UI scorre di nuovo a una bolla precedente o
torna alla visualizzazione della chat, l'app è responsabile della memorizzazione nella cache dell'immagine decodificata
o del mantenimento dei byte; la chiamata di downloadMedia attiva un altro recupero
di rete.
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)
}
}
}
Deviazione (al di fuori dell'orario di lavoro e in caso di sovraccarico)
Il reindirizzamento devia gli utenti quando il team di assistenza non è disponibile. Di solito questo accade in due scenari:
Al di fuori dell'orario di apertura: l'utente tenta di contattare l'assistenza al di fuori dell'orario di apertura configurato.
Capacità eccessiva: la coda è aperta, ma il tempo di attesa supera la soglia configurata dall'amministratore.
In un'architettura headless, l'SDK non blocca automaticamente l'utente né visualizza una schermata di errore. Invece, passa in modo invisibile lo stato di deviazione e le opzioni di fallback configurate dall'amministratore alla tua app. La tua app deve controllare questo stato prima di tentare di avviare una sessione.
Gestire gli stati di deviazione
Cosa fornisce l'SDK: un deflectionFrom: List<QueueMenuDeflection>? su
QueueMenu che elenca le regole di deviazione in entrata che indirizzano altre code
a questa. Ogni QueueMenuDeflection contiene solo due campi:
deflectionType: DeflectionType?: la categoria di deviazione (ad esempio, dopo l'orario di lavoro o per capacità eccessiva).menuId: Int?: l'ID della coda di origine che viene deviata in questa.
QueueMenuDeflection non riporta un messaggio sul display o un'etichetta leggibile. Per il testo rivolto agli utenti (ad esempio Il nostro ufficio è chiuso),
utilizza afterHoursMessage della coda (derivato da
QueueMenu.settings[].afterHours) o le tue stringhe configurate dall'amministratore. Per il nome visualizzato della coda di destinazione risolta, cercalo nell'albero del menu già recuperato tramite menuId.
Cosa crei: la logica per verificare questi stati di deviazione prima di inserire un utente in una coda e la UI personalizzata (ad esempio un avviso a schermo intero o un banner) che mostra il messaggio appropriato e i pulsanti di routing alternativi.
Esempio di implementazione
Il seguente codice intercetta il tentativo di un utente di
avviare una chat. Controlla i metadati di deviazione dell'SDK su QueueMenu e, se
una regola è attiva, mostra una schermata di deviazione utilizzando afterHoursMessage
della coda (o un fallback).
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)
}
}
Opzioni di deviazione del rendering
Quando una regola di deviazione è attiva, gli amministratori configurano probabilmente modi alternativi per consentire all'utente di ricevere assistenza, in genere indirizzandolo a un'altra coda (che puoi risolvere utilizzando menuId) o mostrando la afterHoursMessage configurata della coda corrente.
Cosa fornisce l'SDK: l'elenco delle voci QueueMenuDeflection su
deflectionFrom, ciascuna con un deflectionType e un menuId.
Cosa crei: un menu dinamico che scorre queste voci, risolve
ogni menuId rispetto alla struttura del menu già recuperata per ottenere un nome visualizzato e
genera i pulsanti appropriati.
Esempio di implementazione
Questo codice prende le voci di deviazione e
crea dinamicamente una schermata. Utilizza il nome della coda di origine (risolto da
menuId) come etichetta del pulsante, tornando a deflectionType quando un nome
non è disponibile.
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 è l'elenco delle regole di deviazione che indirizzano a questo menu. Ogni voce menuId identifica una coda di origine che devia verso quella attuale. La tua app decide come esporli. Alcune integrazioni presentano
la coda di destinazione come qui ti invieremo invece, mentre altre
utilizzano la presenza di qualsiasi regola come porta per mostrare una schermata generica riprova più tardi.