In diesem Dokument wird beschrieben, wie Sie das Headless Mobile SDK in Ihre Android-App einbinden und anpassen. Dazu gehören Sprach-, Bildschirmfreigabe-, geplante Anruf- und E-Mail-Funktionen.
Sprachanruf
Das Headless Mobile SDK für Android bietet Unterstützung für Sprachanrufe über die Module CCAICall und CCAICallRed. Ihre App ist für die benutzerdefinierte Anrufoberfläche verantwortlich, während das SDK die Erstellung von Anrufen, die Verarbeitung eingehender Anrufe, die Anbieterintegration und den Anruflaufzeitstatus übernimmt.
Funktionen des SDK: APIs für Anrufdienste zum Starten von Sofortanrufen, Starten von Voicemail, Verarbeiten eingehender Anrufe und Beobachten des Anrufstatus.
Was Sie entwickeln: Die visuelle Anrufoberfläche, einschließlich des Einstiegspunkts vor dem Anruf, der Aufforderung zur Einwilligung in die Aufzeichnung, der Klingel- oder Wartestatus, der Steuerelemente während des Anrufs, der Benutzeroberfläche des Vordergrunddienstes und der Navigation nach dem Anruf.
Anrufmodul initialisieren
Initialisieren Sie das Anrufmodul nach der Initialisierung des Core SDK:
import com.ccaiplatform.ccaikit.CCAI
import com.ccaiplatform.ccaicall.CallOptions
import com.ccaiplatform.ccaicall.initializeCall
CCAI.initialize(context = applicationContext, options = initOptions)
CCAI.initializeCall(
context = applicationContext,
options = CallOptions()
)
VoiceCallChannel-Funktionen prüfen
Bevor Sie einen Sofortanruf anbieten, sehen Sie sich die VoiceCallChannel der Warteschlange an, um zu entscheiden, welche Einstiegspunkte verfügbar sind (Sofortanruf, Voicemail-Fallback, Umleitung auf Channelebene) und ob eine Einwilligung zur Aufzeichnung erforderlich ist.
Wichtige VoiceCallChannel-Felder
instantEnabled:Boolean– Gibt an, ob für diese Warteschlange ein Live-Sprachanruf (Instant) zulässig ist. Behandlefalseals „keine Schaltfläche für Sofortanrufe anbieten“. Im Gegensatz zuvoiceCall != null, wo nur angegeben wird, ob die Warteschlange überhaupt einen Sprachchannel hat.preSessionSmartAction:Boolean– Gibt an, ob für diesen bestimmten Channel vor Beginn eines Anrufs Smart Actions vor der Sitzung erforderlich sind. Informationen zum entsprechenden Pfad auf Warteschlangenebene finden Sie unter Smart Actions vor der Sitzung im Headless Mobile SDK für Android: Smart Actions, Anhänge und Abweisung.scheduleEnabled:Boolean– gibt an, ob geplante Anrufe unterstützt werden. Weitere Informationen finden Sie im Abschnitt Geplante Anrufe.recordingOption:RecordingOption?– das Einwilligungsmodell für Anrufaufzeichnungen. Weitere Informationen finden Sie unter Nutzereinwilligung aufzeichnen. Anders alsRecordingPermission.NOT_ASKED, das nur bei geplanten Anrufen verwendet wird.voicemailReason:String?– wird ausgefüllt, wenn der Channel so konfiguriert ist, dass Anrufe an die Voicemail weitergeleitet werden.phoneNumber:String?– PSTN-Fallbacknummer, die für die Umleitung oder Mailbox konfiguriert ist.scheduleDeflectionType:String?/deflected:Boolean?/deflectedReason:String?– Metadaten zur Ablenkung auf Channelebene. Wenndeflected == true, wird die vom Administrator konfigurierte MeldungdeflectedReasonangezeigt und anstelle eines sofortigen Anrufs wird der konfigurierte Fallback angeboten (z. B. eine Telefonnummer, die gewählt werden kann, oder ein Einstiegspunkt für geplante Anrufe). Weitere Informationen finden Sie im Abschnitt Wait-time deflection (Wartezeitumleitung) im Headless Mobile SDK für Android: Smart Actions, Attachments, and Deflection (Headless Mobile SDK für Android: Smart Actions, Anhänge und Umleitung) für die Warteschlangenumleitung.
fun voiceEntryPoints(menu: QueueMenu) {
val voice = menu.channels.voiceCall
if (voice == null) {
callNowButton.isVisible = false
voicemailButton.isVisible = false
return
}
// Instant call - only when explicitly enabled and not deflected
callNowButton.isVisible = voice.instantEnabled && voice.deflected != true
// Voicemail fallback
val number = voice.phoneNumber
if (voice.voicemailReason != null && number != null) {
voicemailButton.isVisible = true
voicemailButton.text = "Leave a voicemail"
voicemailButton.setOnClickListener {
dialVoicemail(number, reason = voice.voicemailReason)
}
}
// Channel-level deflection
if (voice.deflected == true) {
showChannelDeflection(reason = voice.deflectedReason)
}
}
Einwilligung zur Aufzeichnung
Was das SDK bietet: Ein Feld recordingOption: RecordingOption? auf VoiceCallChannel, das das Einwilligungsmodell für die Anrufaufzeichnung beschreibt, das Administratoren im Portal konfiguriert haben.
Was Sie erstellen: Die Einwilligungserklärung (wenn ASK_USER) und die Verzweigungslogik, die die Auswahl des Administrators berücksichtigt.
enum class RecordingOption {
ALWAYS, // Calls are always recorded - surface a disclosure before connecting
NEVER, // Calls are never recorded - no disclosure needed
ASK_USER // Prompt the user for consent before the call connects
}
fun handleRecordingConsent(voice: VoiceCallChannel, onResult: (Boolean) -> Unit) {
when (voice.recordingOption) {
RecordingOption.ALWAYS -> {
showRecordingDisclosure("This call will be recorded.")
onResult(true)
}
RecordingOption.NEVER, null -> onResult(true)
RecordingOption.ASK_USER -> presentConsentPrompt { granted ->
onResult(granted)
}
}
}
Sofortanruf starten
Bevor Sie einen Sofortanruf starten:
Prüfen Sie die
VoiceCallChannelder ausgewählten Warteschlange.Prüfen Sie, ob Sofortanrufe aktiviert sind und nicht umgeleitet werden.
Die Einwilligung zur Aufzeichnung wird gemäß den
recordingOptionder Warteschlange eingeholt.Mikrofonberechtigung anfordern
Rufen Sie
startInstantCallan.
suspend fun initiateInstantCall(queueMenu: QueueMenu) {
val voice = queueMenu.channels.voiceCall ?: return
if (!voice.instantEnabled || voice.deflected == true) {
return
}
val recordingPermission = resolveRecordingPermission(voice)
requestMicrophonePermissionIfNeeded()
try {
CCAI.callService?.startInstantCall(
menuId = queueMenu.id,
recordingPermission = recordingPermission
)
} catch (e: Exception) {
showCallStartError(e)
}
}
Anrufstatus beobachten
Anrufservice-Ereignisse vor dem Starten oder Annehmen eines Anrufs erfassen:
val service = CCAI.callService ?: return
lifecycleScope.launch {
service.stateChanged.collect { state ->
updateCallConnectionState(state)
}
}
lifecycleScope.launch {
service.callReceived.collect { call ->
updateCallDetails(call)
}
}
lifecycleScope.launch {
service.incomingCallEvent.collect { event ->
handleIncomingCallEvent(event)
}
}
lifecycleScope.launch {
service.waitTimeUpdated.collect { waitTime ->
updateEstimatedWaitTime(waitTime)
}
}
lifecycleScope.launch {
service.participantUpdated.collect { participant ->
updateParticipantInfo(participant)
}
}
lifecycleScope.launch {
service.interruptionDetected.collect { interruption ->
// A PSTN interruption (for example, an incoming cellular call)
// has paused the active CCAI call. Surface a "call interrupted"
// banner and offer a Resume action after the interruption ends.
showInterruptionBanner(interruption)
}
}
lifecycleScope.launch {
service.deflectionOffered.collect { deflection ->
// The platform offered a wait-time deflection (for example, a
// scheduled callback or PSTN fallback). Render the prompt and
// honor the user's choice.
presentWaitTimeDeflection(deflection)
}
}
Anbieterstatus im Vergleich zum Serverstatus
stateChanged und callReceived.status sind unabhängige Signale. Lesen Sie sie als Spalten, nicht als Zeilen. Der stateChanged des Anbieters meldet den lokalen VoIP-Handshake, der serverseitige callReceived.status den Lebenszyklus des Anrufs auf der Plattform. Löse den „Verbunden“-Flip und den Anruftimer aus, wenn callReceived.status erreicht wird, nicht wenn stateChanged den Status „Verbunden“ erreicht.
| Signal | Was wird gemeldet? | Was Sie daraus ableiten können |
|---|---|---|
stateChanged (Anbieter) |
Lokaler VoIP-Handshake – Connecting, Connected (local), Disconnected, Error(...). |
Optionaler Pre-Status-Spinner, bevor der Serverstatus eintrifft, Fehler angezeigt werden und die Bereinigung nach dem Anruf erfolgt. |
callReceived.status (Server) |
Serverseitiger Lebenszyklus: WAITING, CONNECTING, CONNECTED, ON_HOLD, INTERRUPTED, ENDED. |
Verbundene Übergabe, Start des Anruftimers, vom Kundenservicemitarbeiter zugewiesene Benutzeroberfläche, Überlagerungen für Weiterleitung oder Eskalierung und der Ablauf zum Beenden des Anrufs. |
Eingehende Anrufe
Eingehende Sprachanrufe werden über FCM an das Gerät gesendet. Da eingehende Anrufe eingehen können, während Ihre App im Hintergrund ausgeführt wird oder vollständig beendet ist, muss Ihre Integration bereit sein, sie in jedem Prozessstatus zu empfangen.
ausgelöst.Funktionen des SDK: Ein IncomingCallEvent-Ablauf auf callService, der den Lebenszyklus eines eingehenden Anrufs ausgibt: Arrived, Accepted, Rejected. Der Anrufdienst stellt auch acceptIncomingCall() und rejectIncomingCall() zur Verfügung, um auf das Angebot zu reagieren.
Was Sie entwickeln: Die Benutzeroberfläche für eingehende Anrufe (Vollbildschirm für Klingelton, Darstellung auf dem Sperrbildschirm oder In-App-Banner) und die Handler, die das Angebot annehmen oder ablehnen.
Implementierungsbeispiel
Erfassen Sie incomingCallEvent und rendern oder schließen Sie die Benutzeroberfläche bei jedem Lifecycle-Event. Akzeptieren oder lehnen Sie dann aus einem langlebigen Bereich ab.
Definieren Sie applicationScope einmal in Ihrer Application-Klasse, damit die Annahme- oder Ablehnungs-Coroutinen das kurzzeitige Schließen von Activity während des Handshake überstehen.
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeoutOrNull
// Define this in your Application class (or another long-lived singleton).
// Expose it however your app prefers as top-level property, dependency
// injection, or a static field on the Application subclass.
val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
lifecycleScope.launch {
CCAI.callService?.incomingCallEvent?.collect { event ->
when (event) {
is IncomingCallEvent.Arrived -> showIncomingCallUi(event.call)
is IncomingCallEvent.Accepted -> navigateToCallScreen(event.call)
is IncomingCallEvent.Rejected -> dismissIncomingCallUi()
}
}
}
// User taps "Accept" in your custom UI.
fun onAcceptTapped() {
// Important: accept/reject helpers suspend internally. Launch them on
// `applicationScope` (defined above) so the call survives if the
// ringing Activity is dismissed during the handshake.
applicationScope.launch {
CCAI.callService?.acceptIncomingCall()
}
}
fun onRejectTapped() {
applicationScope.launch {
CCAI.callService?.rejectIncomingCall()
}
}
FCM mit der UI für eingehende Anrufe verbinden
Eingehende Anrufe, die über FCM zugestellt werden, kommen in einem Hintergrundthread an, oft bevor ein Activity vorhanden ist. Verwende ein kurzes Zeitlimit, um die FCM-Nutzlast mit der ersten IncomingCallEvent.Arrived-Ausgabe zu verknüpfen, und starte dann die Benutzeroberfläche.
Implementierungsbeispiel
Unterbreche die Fahrt an der nächsten IncomingCallEvent.Arrived mit withTimeoutOrNull und übergib die Navigation dann an deinen Navigator.
suspend fun handleIncomingCallPush() {
val arrived = withTimeoutOrNull(60_000L) {
CCAI.callService?.incomingCallEvent
?.filterIsInstance<IncomingCallEvent.Arrived>()
?.first()
} ?: return
// `myNavigator` is the `IncomingCallUiNavigator` implementation you wire
// up during app initialization (see "Alternative: full-screen ringing
// Activity" below). Hold the reference somewhere long-lived (for
// example, on your `Application` subclass) so it can be reused here.
myNavigator.showRingingUi(applicationContext, arrived.call.id)
}
Integration des Telekommunikations-Frameworks (empfohlen)
Wenn Sie eine integrierte Android-Funktion für eingehende Anrufe nutzen möchten – Vollbild-Klingelton, Darstellung auf dem Sperrbildschirm und Integration in das Systemanrufprotokoll – registrieren Sie einen PhoneAccount und lassen Sie das Telecom-Framework die Klingelton-UX verwalten.
Funktionen des SDK: Ein TelecomCallService-Helfer, der ein PhoneAccount für Ihre App (registerPhoneAccount(context)) registriert, ein TelecomConnectionService zum Überbrücken eingehender CCAI-Anrufe in Android Telecom und eine TelecomConnectionCallback-Schnittstelle für Rückrufe zum Verbindungsstatus.
Was Sie entwickeln: Die einmalige PhoneAccount-Registrierung bei der App-Initialisierung und eine Manifestdeklaration für den Verbindungsdienst.
Implementierungsbeispiel
Registrieren Sie das Telefonkonto beim Start der App und deklarieren Sie dann den Verbindungsdienst und die Berechtigung in Ihrem Manifest.
// In Application.onCreate(), after CCAI.initializeCall(...)
TelecomCallService.registerPhoneAccount(context = this)
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<service
android:name="com.ccaiplatform.android.call.TelecomConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
Alternative: Anrufaktivität im Vollbildmodus
Wenn Sie lieber einen eigenen Anruferbildschirm rendern möchten, anstatt das Telecom-Framework zu verwenden, stellt das SDK IncomingCallService zusammen mit der IncomingCallUiNavigator-Schnittstelle zur Verfügung. Implementieren Sie die Schnittstelle und übergeben Sie Ihre Implementierung dann während der App-Initialisierung an IncomingCallService.
Implementierungsbeispiel
Implementieren Sie IncomingCallUiNavigator und übergeben Sie es einmal während Application.onCreate() an IncomingCallService.
// 1. Implement the navigator. Override the three interface methods to
// declare the ringing Activity intent and to show/hide your UI as the
// call lifecycle changes.
class MyNavigator : IncomingCallUiNavigator {
override fun ringingActivityIntent(
context: Context,
callId: Long,
callerName: String?
): Intent = Intent(context, RingingActivity::class.java).apply {
putExtra("callId", callId)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
override fun showRingingUi(context: Context, callId: Long) {
// Optional: surface an in-app banner or push your ringing UI.
}
override fun hideRingingUi(context: Context, callId: Long) {
// Optional: dismiss whatever you surfaced in showRingingUi.
}
}
// 2. Wire it up during app initialization (typically in Application.onCreate()).
val incomingCallService = IncomingCallService(
applicationContext,
MyNavigator()
)
if (!CCAI.canShowIncomingCallFullScreen(context)) {
CCAI.openFullScreenIntentSettings(context)
}
Dienst im Vordergrund für aktives Klingeln
Während ein Anruf klingelt, kann Android Hintergrundprozesse beenden. Das SDK enthält ein IncomingRingingService, das den Klingeltonablauf als Dienst im Vordergrund aufrechterhält. Deklarieren Sie den Dienst in Ihrem Manifest. Das SDK startet und beendet ihn automatisch, wenn eingehende Anrufe eingehen.
<service
android:name="com.ccaiplatform.android.call.IncomingRingingService"
android:foregroundServiceType="phoneCall"
android:exported="false" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
Anrufstatus und serverseitige Status
Was das SDK bietet: Ein servergesteuertes CallStatus-Enum für jede Call-Nutzlast, das beschreibt, wo sich der Aufruf im Lebenszyklus befindet (z. B. CONNECTING, WAITING, CONNECTED, ON_HOLD, INTERRUPTED, ENDED, FAILED).
Was Sie entwickeln: Die Zuordnung von CallStatus zu den für den Kunden sichtbaren Status in Ihrem Anrufbildschirm.
Implementierungsbeispiel
when-Branch auf call.status für jede callReceived-Ausgabe und Rendern des entsprechenden UI-Zustands:
fun updateCallDetails(call: Call) {
when (call.status) {
CallStatus.CONNECTING -> renderConnectingState()
CallStatus.WAITING -> renderWaitingInQueueState(call.estimatedWait)
CallStatus.CONNECTED -> renderConnectedState(call.agent)
CallStatus.ON_HOLD -> renderOnHoldState()
CallStatus.INTERRUPTED -> renderInterruptedState()
CallStatus.ENDED, CallStatus.FAILED -> renderEndedState(call.endReason)
}
}
Steuerelemente während des Anrufs (Stummschalten, Lautsprecher, Halten)
Funktionen des SDK: Nur-Lese-Attribute für callService – isMuted, isSpeakerEnabled, isOnHold – sowie die Aktionen zum Umschalten der einzelnen Status (setMuted, setSpeakerEnabled, setOnHold).
Was Sie entwickeln: Die Schaltflächen zum Stummschalten, für den Lautsprecher und zum Halten in Ihrer Anruf-Benutzeroberfläche, die mit den Aktionen des SDK verbunden sind und den aktuellen Status widerspiegeln.
Implementierungsbeispiel
Lesen Sie den aktuellen Status aus callService, um Ihre Schaltflächen zu initialisieren, und schalten Sie sie dann um und aktualisieren Sie sie bei Berührung.
muteButton.isSelected = CCAI.callService?.isMuted == true
speakerButton.isSelected = CCAI.callService?.isSpeakerEnabled == true
holdButton.isSelected = CCAI.callService?.isOnHold == true
fun onMuteTapped() {
val next = CCAI.callService?.isMuted != true
CCAI.callService?.setMuted(next)
muteButton.isSelected = next
}
fun onSpeakerTapped() {
val next = CCAI.callService?.isSpeakerEnabled != true
CCAI.callService?.setSpeakerEnabled(next)
speakerButton.isSelected = next
}
fun onHoldTapped() {
val next = CCAI.callService?.isOnHold != true
CCAI.callService?.setOnHold(next)
holdButton.isSelected = next
}
Laufenden Anruf nach einer Unterbrechung fortsetzen
Wenn ein PSTN-Unterbrechung (z. B. ein eingehender Mobilfunkanruf) den aktiven CCAI-Anruf pausiert, hält das SDK die Sitzung aktiv und gibt interruptionDetected aus.
Nach dem Ende der Unterbrechung kann Ihre App den Anruf fortsetzen.
Funktionen des SDK: getLastCallInProgress() zum Suchen nach einem laufenden Anruf (auch nach Neustarts der App) und resumeCall(call), um ihn in den Vordergrund zu holen.
Was Sie entwickeln: Die Möglichkeit zum Fortsetzen in Ihrer Benutzeroberfläche (in der Regel ein Banner oder ein automatisches Fortsetzen nach dem Ende der Unterbrechung) und die Navigation zurück zum Anrufbildschirm.
Implementierungsbeispiel
Suchen Sie mit getLastCallInProgress nach dem laufenden Anruf, setzen Sie ihn fort und kehren Sie zum Anrufbildschirm zurück.
lifecycleScope.launch {
val call = CCAI.callService?.getLastCallInProgress() ?: return@launch
try {
CCAI.callService?.resumeCall(call)
navigateToCallScreen(call)
} catch (e: Exception) {
showCallResumeError(e)
}
}
Eskalierung durch einen virtuellen Kundenservicemitarbeiter
Was das SDK bietet: canEscalate(allowSkipVirtualAgent: Boolean) ist eine Methode für CallResponse (das von callService.callReceived ausgegebene Modell), nicht für callService selbst. Es wird gemeldet, ob der aktuelle Anruf basierend auf dem aktuellen Status des virtuellen Kundenservicemitarbeiters für die Weiterleitung an einen menschlichen Kundenservicemitarbeiter infrage kommt.
Was Sie entwickeln: Behalten Sie die letzte callReceived-Ausgabe als „aktuellen Anruf“ bei und binden Sie die Sichtbarkeit der Schaltfläche „Mit einer Person sprechen“ an currentCall?.canEscalate(...). Das Argument allowSkipVirtualAgent ist ein Menükonfigurationswert, den Sie separat aus der Antwort des Aufrufs abrufen. Codieren Sie true nicht fest in der Produktion.
Implementierungsbeispiel
Bewahren Sie die aktuelle Call in einer Property auf und rufen Sie dann canEscalate dafür auf. Entfernen Sie allowSkipVirtualAgent aus Ihrer Menükonfiguration.
// Held from the most recent `callReceived` emission
private var currentCall: Call? = null
// `allowSkipVirtualAgent` is fetched separately from menu configuration
// it is not derived from the CallResponse.
val allowSkip = menuConfiguration.allowSkipVirtualAgent
val allowed = currentCall?.canEscalate(allowSkipVirtualAgent = allowSkip) == true
talkToAgentButton.isVisible = allowed
Mailbox
Was das SDK bietet: startVoicemail(VoicemailRequest) auf callService sowie ein VoicemailReason-Enum, das beschreibt, warum die Plattform Voicemail angeboten hat (z. B. Fallback nach Feierabend, Fallback bei Überlastung oder vorübergehende Weiterleitung).
Was Sie entwickeln: Der Einstiegspunkt für die Voicemail in Ihrer Benutzeroberfläche (wird in der Regel angezeigt, wenn VoiceCallChannel.voicemailReason ausgefüllt ist oder wenn der Anrufdurchlauf zu einem Voicemail-Ergebnis führt) und die Aufzeichnungsoberfläche selbst.
Implementierungsbeispiel
Zeige die Schaltfläche für die Mailbox an, wenn voicemailReason festgelegt ist, und rufe dann startVoicemail mit einem VoicemailRequest auf.
enum class VoicemailReason {
AFTER_HOUR_DEFLECTION,
OVER_CAPACITY_DEFLECTION,
TEMPORARY_REDIRECTION
}
suspend fun offerVoicemail(menu: QueueMenu) {
val voice = menu.channels.voiceCall ?: return
if (voice.voicemailReason == null) return
try {
CCAI.callService?.startVoicemail(
VoicemailRequest(menuId = menu.id)
)
} catch (e: Exception) {
showVoicemailError(e)
}
}
Verkürzung der Wartezeit
Wenn die Warteschlange geöffnet ist, die Wartezeit jedoch den vom Administrator konfigurierten Grenzwert überschreitet, kann die Plattform einen alternativen Pfad anbieten, in der Regel einen geplanten Rückruf oder eine PSTN-Umleitung.
Was das SDK bietet: Eine CallDeflection-Nutzlast, die die angebotene Alternative beschreibt und mit getCallDeflection(callId) auf callService abgerufen werden kann. Die Plattform gibt dieselbe Nutzlast auch über den deflectionOffered-Ablauf aus, wenn während des Wartens eine Umleitung verfügbar wird. Ein separates CompanyResponse.preventDirectPstnCall-Flag gibt an, ob die direkte PSTN-Wahl in Ihrer Fallback-Benutzeroberfläche ausgeblendet werden soll.
Was Sie erstellen: Den Umleitungs-Prompt (z. B. „Die geschätzte Wartezeit beträgt 12 Minuten. Möchten Sie stattdessen einen Rückruf planen?“) und die Verzweigungslogik, die die Auswahl des Nutzers berücksichtigt.
Implementierungsbeispiel
Rufen Sie die angebotene Umleitung mit der ID des aktiven Anrufs ab, kombinieren Sie sie mit CompanyResponse.preventDirectPstnCall und präsentieren Sie den Prompt.
lifecycleScope.launch {
val currentCall = CCAI.callService?.getLastCallInProgress()
?: return@launch
val deflection = CCAI.callService?.getCallDeflection(callId = currentCall.id)
?: return@launch
val company = CCAI.companyService?.get()
val allowDirectPstn = company?.preventDirectPstnCall != true
presentWaitTimeDeflection(deflection, allowDirectPstn)
}
Erweitert CallOptions
CallOptions bietet eine kleine Anzahl von Ein/Aus-Schaltern für Apps, die die Standardanrufeinstellungen anpassen müssen.
connectingPollIntervalMs:Long– wie oft das SDK die Plattform abfragt, während sich der Anruf im Verbindungsstatus befindet.connectedPollIntervalMs:Long– wie oft das SDK die Plattform abfragt, während der Anruf verbunden ist.endCallMaxRetries:Int– gibt an, wie oft das SDK die Anfrage zum Beenden des Anrufs wiederholt, bevor ein Fehler angezeigt wird.endCallRetryDelayMs:Long– Verzögerung zwischen den Wiederholungsversuchen für das Beenden des Anrufs.
Implementierungsbeispiel
Übergeben Sie die optimierten Werte an CallOptions, wenn Sie das Anrufmodul initialisieren:
CCAI.initializeCall(
context = this,
options = CallOptions(
connectingPollIntervalMs = 2_000L,
connectedPollIntervalMs = 5_000L,
endCallMaxRetries = 3,
endCallRetryDelayMs = 1_000L
)
)
Bildschirm freigeben
Durch die Bildschirmfreigabe können Kundenservicemitarbeiter den Bildschirm des Nutzers sehen und so bei der Fehlerbehebung helfen. Die CCAI Platform unterstützt zwei Modi für die Bildschirmfreigabe:
In-App-Freigabe: Der KI-Agent kann nur sehen, was in Ihrer jeweiligen App passiert.
Freigabe des gesamten Geräts (optional): Der KI-Agent kann das gesamte Gerät des Nutzers sehen, einschließlich des Android-Startbildschirms und anderer Apps. Dazu ist eine erweiterte Android-Konfiguration mit
MediaProjectionund einem Vordergrunddienst erforderlich.
Funktionen des SDK: Das CCAIScreenShare-Modul, das Sitzungen erstellt und verwaltet, Statusänderungen verarbeitet und Einwilligungsanfragen bearbeitet.
Was Sie entwickeln: Die Initialisierungskonfiguration, die UI-Aufforderungen zur Einwilligung des Nutzers und (falls Sie die Unterstützung des gesamten Geräts anbieten) die integrierte Android-MediaProjection-Integration mit einem Dienst im Vordergrund.
Der Bildschirmfreigabedienst bietet Methoden für den gesamten Sitzungslebenszyklus – startSession, activateSession, stopSession – sowie Konfigurationsmethoden für die Fernbedienung und die Freigabe des gesamten Geräts. In den nächsten Unterabschnitten wird der grundlegende Integrationsablauf beschrieben.
Vollständige Methodensignaturen und Parameterdetails für alle Methoden zur Bildschirmfreigabe finden Sie in der Headless Mobile SDK – Android API-Referenz.
Bildschirmfreigabe initialisieren und konfigurieren
Initialisieren Sie den Bildschirmfreigabedienst während der App-Einrichtung, indem Sie Ihren Bildschirmfreigabeschlüssel angeben:
import com.ccaiplatform.android.CCAI
import com.ccaiplatform.android.ScreenShareOptions
// Minimal - domain defaults to your instance's configured provider
CCAI.initializeScreenShare(
context = this,
screenShareOptions = ScreenShareOptions(
key = "YOUR_SCREEN_SHARE_KEY"
)
)
// If your instance or provider requires an explicit domain:
// CCAI.initializeScreenShare(
// context = this,
// screenShareOptions = ScreenShareOptions(
// key = "YOUR_SCREEN_SHARE_KEY",
// domain = "your_subdomain.ccaiplatform.com"
// )
// )
Voraussetzungen für die Bildschirmfreigabe prüfen
Bevor Sie die Bildschirmfreigabe aktivieren, prüfen Sie, ob sie für den aktuellen Chat verfügbar ist:
import com.ccaiplatform.ccaiscreenshare.ScreenShareManager
fun isScreenShareEnabled(chat: ChatResponse): Boolean {
return CCAI.screenShareService != null
&& chat.supportScreenShare == true
}
Anfragen zur Bildschirmfreigabe bearbeiten
Implementieren Sie die Verarbeitung von Bildschirmfreigabeanfragen mit ScreenShareManager. Sie erstellen eine Sitzung und verarbeiten die asynchrone Antwort mit ScreenShareCallbacks:
import com.ccaiplatform.ccaiscreenshare.ScreenShareManager
import com.ccaiplatform.ccaiscreenshare.ScreenShareCallbacks
fun requestScreenShare(chatId: String, isFromRemote: Boolean) {
val screenShareService = CCAI.screenShareService
if (screenShareService == null) {
Log.e("ScreenShare", "Screen share service not available")
return
}
// Track whether this session is agent-initiated
screenShareInitiatedFrom = if (isFromRemote) "agent" else "endUser"
if (!isFromRemote) {
sendScreenShareMessage(event = "screenShareRequestedFromEndUser")
}
val callbacks = ScreenShareCallbacks(
onSessionStateChanged = { state ->
Log.d("ScreenShare", "State changed: $state")
handleScreenShareStateChange(state)
},
onSessionCreationError = { error ->
Log.e("ScreenShare", "Session creation failed: ${error.message}")
sendScreenShareMessage(event = "screenShareFailed")
showErrorToast("Screen share failed to start.")
},
onSessionActivationRequest = {
Log.d("ScreenShare", "Activation request received")
// For agent-initiated sessions, activate immediately
// For user-initiated sessions, show a confirmation dialog first
if (screenShareInitiatedFrom == "agent") {
ScreenShareManager.activateSession()
} else {
showScreenShareConsentDialog {
ScreenShareManager.activateSession()
}
}
}
)
ScreenShareManager.startSession(
request = ScreenShareRequest(
communicationId = chatId,
communicationType = CommunicationType.Chat,
initiatedFrom = if (isFromRemote) ScreenShareFrom.AGENT else ScreenShareFrom.END_USER
),
callbacks = callbacks
)
}
Änderungen des Bildschirmfreigabestatus verarbeiten
Änderungen des Status der Bildschirmfreigabesitzung über den onSessionStateChanged-Callback, der für ScreenShareCallbacks bereitgestellt wird, überwachen:
fun handleScreenShareStateChange(state: ScreenShareSessionState) {
currentScreenShareSessionState = state
when (state) {
ScreenShareSessionState.INACTIVE -> {
Log.d("ScreenShare", "No active session")
}
ScreenShareSessionState.PENDING -> {
Log.d("ScreenShare", "Session pending - waiting for activation")
// For agent-initiated sessions, activate immediately
if (screenShareInitiatedFrom == "agent") {
ScreenShareManager.activateSession()
}
}
ScreenShareSessionState.ACTIVE -> {
Log.d("ScreenShare", "Screen share active")
sendScreenShareMessage(event = "screenShareStarted")
updateUiForActiveScreenShare()
}
}
}
ScreenShareCallbacks-Referenz
| Rückruf | Beschreibung |
|---|---|
onSessionStateChanged |
Wird aufgerufen, wenn sich der Status der Bildschirmfreigabesitzung ändert (INACTIVE, PENDING, ACTIVE). |
onSessionCreationError |
Wird aufgerufen, wenn das Erstellen der Sitzung fehlschlägt (z. B. aufgrund eines Netzwerkproblems oder weil der Dienst nicht verfügbar ist). |
onSessionActivationRequest |
Wird aufgerufen, wenn die Sitzung zur Aktivierung bereit ist. Rufen Sie ScreenShareManager.activateSession() auf, um die Freigabe zu starten. Bei vom Nutzer initiierten Sitzungen muss zuerst ein Einwilligungs-Prompt angezeigt werden. Bei vom KI-Agenten initiierten Sitzungen wird die Funktion sofort aktiviert. |
onSessionRemoteControlRequest |
Wird aufgerufen, wenn der Agent die Fernsteuerung des Bildschirms des Nutzers anfordert. Vor dem Gewähren der Fernbedienung eine Aufforderung zur Einwilligung anzeigen |
onSessionFullDeviceRequest | Wird aufgerufen, wenn der Kundenservicemitarbeiter die Freigabe des gesamten Geräts anfordert (siehe Freigabe des gesamten Geräts unten). Lösen Sie als Reaktion darauf Ihren MediaProjection-Einwilligungsablauf aus. |
onSessionDidSucceed |
Wird aufgerufen, nachdem eine Sitzung erfolgreich aktiviert wurde und das Streaming beginnt. Verwenden Sie diese Methode, um die Benutzeroberfläche auf den Status „Bildschirmfreigabe aktiv“ zu aktualisieren. |
Die vollständigen Enumerationen für den Status und alle zugehörigen Typen finden Sie in der Headless Mobile SDK – Android API Reference.
Freigabe des gesamten Gerätebildschirms (erweitert)
Mit der Bildschirmfreigabe für das gesamte Gerät können Kundenservicemitarbeiter Bildschirme von Anwendungen außerhalb Ihrer eigenen sehen, einschließlich Systemeinstellungen und Navigation zwischen Anwendungen.
Dazu müssen Sie die MediaProjection API von Android einbinden und einen Dienst im Vordergrund ausführen.
Berechtigungen und Dienste im Vordergrund deklarieren
Fügen Sie Ihrer AndroidManifest.xml die erforderlichen Berechtigungen und die Erklärung zum Dienst im Vordergrund hinzu:
<manifest>
<!-- Required for screen capture -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<application>
<service
android:name=".screenshare.ScreenShareForegroundService"
android:foregroundServiceType="mediaProjection"
android:exported="false" />
</application>
</manifest>
Einwilligung für MediaProjection anfordern
Bevor Sie den Bildschirm aufnehmen, müssen Sie den Nutzer mit MediaProjectionManager um die Einwilligung bitten:
import android.media.projection.MediaProjectionManager
import android.content.Context
private val mediaProjectionManager by lazy {
getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
}
fun requestScreenCapturePermission() {
val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
screenCaptureResultLauncher.launch(captureIntent)
}
// Handle the result in your Activity
private val screenCaptureResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
// User granted screen capture permission
startScreenShareForegroundService(result.resultCode, result.data!!)
} else {
Log.d("ScreenShare", "User denied screen capture permission")
}
}
Dienst im Vordergrund implementieren
Erstellen Sie einen Dienst im Vordergrund, der die MediaProjection-Sitzung enthält:
import android.app.Service
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Intent
import android.os.IBinder
class ScreenShareForegroundService : Service() {
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = buildNotification()
startForeground(NOTIFICATION_ID, notification)
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
private fun createNotificationChannel() {
val channel = NotificationChannel(
CHANNEL_ID,
"Screen Share",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Active screen share session"
}
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
private fun buildNotification(): Notification {
return Notification.Builder(this, CHANNEL_ID)
.setContentTitle("Screen Sharing")
.setContentText("An agent is viewing your screen.")
.setSmallIcon(R.drawable.ic_screen_share)
.build()
}
companion object {
private const val CHANNEL_ID = "screen_share_channel"
private const val NOTIFICATION_ID = 1001
}
}
Vollständige Gerätefreigabe aktivieren
Nachdem MediaProjection aktiviert wurde, aktivieren Sie die Freigabe des gesamten Geräts über den Bildschirmfreigabedienst:
// After MediaProjection consent is granted and foreground service is started
ScreenShareManager.enableFullDeviceSharing(true)
Jetpack Compose-Überlegungen zur Bildschirmfreigabe
Empfohlene Ansätze für die Remote-Interaktion in Jetpack Compose
Wichtige interaktive Elemente mit Android-Ansichten umschließen: Bereiche, die per Fernzugriff angeklickt werden müssen, sollten mit der
AndroidView-Composable als Standard-Android-Ansichtssteuerelemente (z. B.android.widget.Button) umschlossen werden. So wird sichergestellt, dass Remote-Klicks über den Android-View-basierten Pfad zuverlässig funktionieren.Remote-Berührungen mit benutzerdefinierter Berührungsbehandlung weiterleiten: Für Compose-Bereiche, die nicht durch Views ersetzt werden können, verwenden Sie benutzerdefinierte Berührungs-Callbacks, um Remote-Berührungsereignisse an Ihre Compose-Logik weiterzuleiten.
Anleitung oder Fallback auf Produktebene bereitstellen: Geben Sie auf den Compose-Bildschirmen deutlich an, dass „Kundenservicemitarbeiter nur ansehen können; Nutzer müssen lokal tippen“ oder schränken Sie die Berechtigungen für die Fernbedienung auf diesen Seiten automatisch ein, während der Lesemodus beibehalten wird.
Beispiel für die Implementierung eines Android-View-Wrappers
import androidx.compose.runtime.Composable
import androidx.compose.ui.viewinterop.AndroidView
import android.widget.Button
@Composable
fun RemoteControlButton(
title: String,
onClick: () -> Unit
) {
AndroidView(
factory = { context ->
Button(context).apply {
text = title
setOnClickListener { onClick() }
}
},
update = { button ->
button.text = title
}
)
}
// Usage in a Composable
@Composable
fun ScreenShareSupportScreen() {
Column {
Text("This screen supports remote control")
RemoteControlButton(title = "Clickable Button") {
println("Button tapped remotely or locally")
}
}
}
Geplante Anrufe
Mit geplanten Anrufen können Nutzer einen zukünftigen Sprachanruf buchen, anstatt in der Warteschlange für einen sofortigen Anruf zu warten. Unter Android bietet das SDK Call-Service-Methoden zum Prüfen der Verfügbarkeit, Abrufen von Zeitfenstern, Buchen, Verschieben und Abbrechen von Anrufen sowie zum Beheben von Planungsfehlern.
Funktionserkennung
Was das SDK bietet: Ein scheduleEnabled-Flag für VoiceCallChannel, das angibt, ob die ausgewählte Warteschlange die Buchung zukünftiger Anrufe unterstützt.
Was Sie entwickeln müssen: Die Logik zum Prüfen dieses Flags, bevor der Einstiegspunkt für die Terminplanung angezeigt wird.
Implementierungsbeispiel
Prüfe scheduleEnabled im Sprachanruf-Warteschlangenkanal. Verwenden Sie voiceCall != null nicht als Proxy. Eine Warteschlange kann sofortige Audioanrufe unterstützen, ohne geplante Anrufe zu unterstützen, und umgekehrt.
fun setupScheduledCallEntryPoint(queueMenu: QueueMenu) {
val voiceChannel = queueMenu.channels.voiceCall
if (voiceChannel == null) {
callNowButton.isVisible = false
scheduleCallButton.isVisible = false
return
}
callNowButton.isVisible = voiceChannel.instantEnabled && voiceChannel.deflected != true
scheduleCallButton.isVisible = voiceChannel.scheduleEnabled == true
}
Optionale Verfügbarkeitsprüfung
Bevor Sie die Zeitauswahl anzeigen, können Sie prüfen, ob für die ausgewählte Warteschlange freie Terminblöcke verfügbar sind.
Implementierungsbeispiel
Verwenden Sie hasScheduledTimeSlots(menuId:) als einfache Vorabprüfung. Wenn false zurückgegeben wird, zeigen Sie stattdessen einen leeren Status oder eine Fallback-Option an, anstatt die Auswahl zu öffnen.
lifecycleScope.launch {
try {
val available = CCAI.callService?.hasScheduledTimeSlots(menuId = menu.id) == true
if (available) {
showScheduleCallUI()
} else {
showNoSlotsMessage()
}
} catch (e: Exception) {
Log.e("CCAI", "Availability check failed: ${e.message}")
showNoSlotsMessage()
}
}
Zeitfenster abrufen
Verfügbare Terminzeiten für die ausgewählte Warteschlange abrufen. Das SDK gibt Datumswerte zurück, die Ihre App in der lokalen Zeitzone des Nutzers anzeigen kann.
Was das SDK bietet: Verfügbare Zeitstempel für Slots für die ausgewählte Warteschlange mit optionaler Unterstützung für das Verschieben eines vorhandenen geplanten Anrufs.
Was Sie entwickeln: Kalender, Zeitauswahl, Zeitzonendarstellung, Verarbeitung von leeren Status und Wiederholungsverhalten.
Implementierungsbeispiel
Rufen Sie getScheduledTimeSlots mit der ID des Warteschlangenmenüs auf.
Geben Sie beim Verschieben die vorhandene ID des geplanten Anrufs an, damit der Dienst die entsprechende Verfügbarkeit zurückgeben kann.
lifecycleScope.launch {
try {
val slots: List<Date> = CCAI.callService?.getScheduledTimeSlots(
menuId = menu.id,
callIdForRescheduling = null
) ?: emptyList()
if (slots.isEmpty()) {
showNoSlotsMessage()
} else {
displayTimeSlotPicker(slots)
}
} catch (e: Exception) {
Log.e("CCAI", "Failed to fetch time slots: ${e.message}")
}
}
Übergeben Sie beim Verschieben die vorhandene ID des geplanten Anrufs:
val slots = CCAI.callService?.getScheduledTimeSlots(
menuId = menu.id,
callIdForRescheduling = existingCallId
) ?: emptyList()
Geplanten Anruf erstellen
Nachdem der Nutzer einen Zeitraum ausgewählt hat, erstellen Sie den geplanten Anruf über callService.
Funktionen des SDK: Eine createScheduledCall-API, mit der der ausgewählte Termin gebucht und der Datensatz für den geplanten Anruf zurückgegeben wird.
Was Sie entwickeln: E.164-Telefonnummernvalidierung, Bestätigungs-UI, lokale Speicherung der ID des geplanten Anrufs und Wiederherstellung bei Zeitkonflikten oder Antworten außerhalb der Geschäftszeiten.
Implementierungsbeispiel
Übergeben Sie den ausgewählten Slot, die Telefonnummer, die Warteschlangen-ID und RecordingPermission.NOT_ASKED in einem ScheduledCallRequest.
lifecycleScope.launch {
try {
val response = CCAI.callService?.createScheduledCall(
ScheduledCallRequest(
menuId = menu.id,
phoneNumber = userPhoneNumber,
scheduleTime = selectedSlot,
recordingPermission = RecordingPermission.NOT_ASKED,
callIdForRescheduling = null,
customData = null,
ticketId = null
)
)
saveScheduledCallId(response?.id)
showConfirmation(scheduledAt = response?.scheduledAt)
} catch (e: CallCreationError.AfterHours) {
showMessage(e.displayMessage)
refreshTimeSlots()
} catch (e: Exception) {
Log.e("CCAI", "Failed to schedule call: ${e.message}")
}
}
Die Telefonnummer muss im E.164-Format angegeben werden, z. B. +14155551234.
Vorhandenes Gespräch verschieben
Wenn Sie einen Termin verschieben möchten, rufen Sie die verfügbaren Zeitblöcke ab und erstellen Sie den neuen geplanten Anruf mit der vorhandenen ID des geplanten Anrufs.
Was das SDK bietet: Die gleichen APIs für Zeitfenster und Erstellung, wobei callIdForRescheduling zur Identifizierung der vorhandenen Buchung verwendet wird.
Was Sie entwickeln: Die Benutzeroberfläche zum Ändern der Uhrzeit, die lokale Suche nach der gespeicherten ID für geplante Anrufe und der aktualisierte Bestätigungsstatus nach der Erstellung der neuen Buchung.
Implementierungsbeispiel
Übergeben Sie die vorhandene Anruf-ID an getScheduledTimeSlots und createScheduledCall.
lifecycleScope.launch {
try {
val slots = CCAI.callService?.getScheduledTimeSlots(
menuId = menu.id,
callIdForRescheduling = existingCallId
) ?: emptyList()
val response = CCAI.callService?.createScheduledCall(
ScheduledCallRequest(
menuId = menu.id,
phoneNumber = userPhoneNumber,
scheduleTime = newSelectedSlot,
recordingPermission = RecordingPermission.NOT_ASKED,
callIdForRescheduling = existingCallId,
customData = null,
ticketId = null
)
)
saveScheduledCallId(response?.id)
showConfirmation(scheduledAt = response?.scheduledAt)
} catch (e: CallCreationError.AfterHours) {
showMessage(e.displayMessage)
refreshTimeSlots()
} catch (e: Exception) {
Log.e("CCAI", "Failed to reschedule: ${e.message}")
}
}
Geplanten Anruf abbrechen
Wenn der Nutzer einen anstehenden Anruf abbricht, rufen Sie cancelScheduledCall auf und löschen Sie die lokal gespeicherte Anruf-ID.
Was das SDK bietet: Eine API zum Abbrechen einer geplanten Anruf-ID.
Was Sie entwickeln: Die Bestätigungsaufforderung, die Benutzeroberfläche für erfolgreiche oder fehlgeschlagene Kündigungen und das Bereinigen aller lokal gespeicherten Status für anstehende Anrufe.
Implementierungsbeispiel
Rufen Sie cancelScheduledCall(callId:) mit der gespeicherten ID auf und entfernen Sie die ID dann aus dem Speicher Ihrer App.
lifecycleScope.launch {
try {
CCAI.callService?.cancelScheduledCall(callId = existingCallId)
clearScheduledCallId()
showCancellationConfirmation()
} catch (e: Exception) {
Log.e("CCAI", "Failed to cancel call: ${e.message}")
}
}
Was das SDK bietet: Die für diese bestimmte Warteschlange konfigurierte Ziel-E-Mail-Adresse sowie alle Anweisungen, die der Administrator dem Nutzer anzeigen möchte (z. B. Geben Sie Ihre Kontonummer an.).
Was Sie entwickeln: Der Bildschirm für das Verfassen von E‑Mails und die Logik zum Erfassen des Abschlusses. Sie können ein eigenes benutzerdefiniertes Formular erstellen oder die integrierte Intent.ACTION_SENDTO-Funktion von Android verwenden, um den bevorzugten E‑Mail-Client des Nutzers zu starten.
Implementierungsbeispiel
Mit diesem Code wird die Support-E-Mail-Adresse aus dem Channels-Objekt des SDK auf der QueueMenu abgerufen. Dadurch wird der integrierte Android-E‑Mail-Client geöffnet und der Fall abgedeckt, in dem kein E‑Mail-Client installiert ist.
import android.content.Intent
import android.net.Uri
import android.widget.Toast
class SupportActivity : AppCompatActivity() {
// 1. Trigger the email flow
fun openEmailComposer(queueMenu: QueueMenu) {
val emailChannel = queueMenu.channels.email ?: return
val emailAddress = emailChannel.email ?: return
val emailIntent = Intent(Intent.ACTION_SENDTO).apply {
data = Uri.parse("mailto:")
putExtra(Intent.EXTRA_EMAIL, arrayOf(emailAddress))
putExtra(Intent.EXTRA_SUBJECT, "Support Request: ${queueMenu.name ?: ""}")
// If the queue provides an instruction message, include it in the body
// putExtra(Intent.EXTRA_TEXT, emailChannel.instructionMessage ?: "")
}
if (emailIntent.resolveActivity(packageManager) != null) {
emailResultLauncher.launch(emailIntent)
} else {
Toast.makeText(
this,
"No email client installed on this device.",
Toast.LENGTH_LONG
).show()
}
}
// 2. Track the outcome using ActivityResultContract
private val emailResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
// Note: Most Android email clients don't return a meaningful
// result code. Unlike iOS's MFMailComposeViewControllerDelegate,
// Android's email intent doesn't reliably report whether the
// email was sent, saved as draft, or cancelled.
// Log this event to your analytics and assume the user
// interacted with the email composer.
Log.d("CCAI", "Email composer closed. Result code: ${result.resultCode}")
MyAnalytics.logEvent("email_composer_closed")
}
}
Externe Umleitungslinks
Was das SDK bietet: Eine Liste von URLs (z. B. die Hilfe Ihres Unternehmens, die Erstattungsrichtlinie oder eine Partner-App) und deren Anzeigetitel, genau wie im Admin-Portal konfiguriert.
Was Sie entwickeln: Die Logik zum Ausführen dieser Links, wenn ein Nutzer darauf tippt – entweder durch Öffnen in einem In-App-Browser (mit benutzerdefinierten Tabs von Android) oder durch Starten des Standardbrowsers des Systems.
Beispiel für die Implementierung (benutzerdefinierte Tabs – empfohlen)
Wenn der Nutzer in Ihrem Menü auf einen externen Link tippt, wird mit diesem Code der vom SDK bereitgestellte URL-String extrahiert und die Webseite in einem In-App-Browser mit Chrome Custom Tabs angezeigt. Benutzerdefinierte Tabs sind schneller und nahtloser als das Starten der vollständigen Browser-App. Gleichzeitig wird die Adressleiste angezeigt, damit der Nutzer weiß, dass er externe Inhalte aufruft.
import androidx.browser.customtabs.CustomTabsIntent
import android.net.Uri
fun openHelpCenterLink(urlString: String) {
val uri = Uri.parse(urlString) ?: return
val customTabsIntent = CustomTabsIntent.Builder()
.setShowTitle(true)
.build()
customTabsIntent.launchUrl(this, uri)
}
Fallback-Implementierung (Systembrowser): Wenn benutzerdefinierte Tabs nicht verfügbar sind oder Sie einen einfacheren Ansatz bevorzugen, können Sie den Link im Standardbrowser des Nutzers öffnen:
import android.content.Intent
import android.net.Uri
fun openHelpCenterLink(urlString: String) {
val uri = Uri.parse(urlString) ?: return
val intent = Intent(Intent.ACTION_VIEW, uri)
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
} else {
showErrorToast("No browser available to open this link.")
}
}
Beispiel für eine vollständige Integration
Mit diesem Code wird der Link zur Abwehr von Einwänden aus dem Channels-Objekt des SDK gelesen und dem Nutzer als anklickbarer Button in deinem Channelmenü präsentiert.
fun setupDeflectionLinks(queueMenu: QueueMenu) {
val deflectionLink = queueMenu.channels.externalDeflectionLink
if (deflectionLink != null) {
helpCenterButton.isVisible = true
helpCenterButton.setOnClickListener {
// Read the URL from the structured ExternalDeflectionLink type
val url = deflectionLink.url ?: return@setOnClickListener
openHelpCenterLink(url)
}
} else {
helpCenterButton.isVisible = false
}
}
ExternalDeflectionLink macht sowohl url- als auch displayName-Felder der obersten Ebene und ein inneres links: List<ExternalDeflection>?-Array verfügbar. Im vorherigen Beispiel wird nur der Eintrag auf oberster Ebene gerendert, was für Warteschlangen mit einem einzelnen Link ausreicht. Wenn Ihre Administratoren mehrere externe Links für eine einzelne Warteschlange konfigurieren, durchlaufen Sie deflectionLink.links und rendern Sie eine Schaltfläche für jeden aktivierten Eintrag. Jedes ExternalDeflection enthält ein eigenes url und displayName:
deflectionLink.links
?.filter { it.enabled == true }
?.forEach { link ->
val button = MaterialButton(this).apply {
text = link.displayName
setOnClickListener { openHelpCenterLink(link.url) }
}
helpCenterContainer.addView(button)
}