Skip to main content

Danamon Enhancement Mobile Development Guide - Android

Attached data fields on the create call

Each EnvironmentCallAttribute declared for an environment becomes one input on the create call page, and its value is sent as an entry of DialCallParameters.attributes (the Attributes object of the dialex request).

Key Label Values
video_flow Video Flow ETB, NTB
video_channel Video Channel Registration, Reactivation, Activation, Saving (2.0), Credit Card
video_application Video Application DBP 2.0
video_language Video Language id, en

Backward compatibility. Older clients do not send these attributes at all. The service fills in its own defaults for them (video_flow = NTB, video_channel = Saving (1.0), video_application = DBP 1.0, video_language = id). The 1.0 values exist only as those service side defaults, which is why they are not offered on the create call page — a current client always sends the values explicitly.

Showing Queue Position and Estimated Wait Time

The SDK can report the caller's live queue position and estimated wait time (EWT) while the call is waiting in the queue. It is off by default; enable it with one configuration flag and listen for one event.

What the SDK does

The SDK has three mutually exclusive ways of waiting for the queue. They are picked in a fixed order of precedence:

# Mode Enabled by Reports position / wait time
1 Queue socket useQueueSocket = true no
2 Queue info polling useQueueSocket = false and useQueueInfo = true yes
3 Legacy waitforroominterval polling both flags false no

In queue info mode the SDK polls /api/WaitForRoomQueueInterval and emits a QueueInfoUpdated event on every poll. It still raises AgentConnected, CallEnded and Error exactly as before — nothing else in your call flow changes.

Position and wait time come from the Genesys URS statistics through the service.

Enable the feature

val sdkConfig = ECVSdkConfiguration(
    serviceUrl = "https://your-ecv-service",
    useQueueSocket = false,         // the queue socket takes precedence, so turn it off
    useQueueInfo = true,            // queue position + estimated wait time
    queueInfoIntervalDelay = 3000   // poll interval in ms, optional (default 3000)
)

Java:

ECVSdkConfiguration config = ECVSdkConfiguration.builder("https://your-ecv-service")
        .useQueueSocket(false)
        .useQueueInfo(true)
        .build();

useQueueInfo is ignored while useQueueSocket is true. If you leave the queue socket on, the call still works — you simply get no QueueInfoUpdated events.

Per-call override

Both flags can also be set per call, on the VideoCall object. A null override (the default) falls back to the SDK configuration; a non-null value wins for that call only. Set them after createCall() and before start() — they are read when the queue wait begins:

val call = sdkInstance.createCall()
call.useQueueSocketOverride = false   // this call: no socket
call.useQueueInfoOverride = true      // this call: queue info polling
call.start(params)

Java:

VideoCall call = sdkInstance.createCall();
call.setUseQueueSocketOverride(false);
call.setUseQueueInfoOverride(true);
call.start(params);

The same precedence applies to the effective values: socket > queue info > legacy. So to get queue info mode for a single call while the global configuration uses the socket, override both flags as above — overriding only useQueueInfoOverride is not enough, because the socket still wins.

If your service exposes the endpoint under a different path, override it with queueInfoEndpoint (default /api/WaitForRoomQueueInterval?rtcId=).

In the demo both flags are per environment. They are declared in SerializableECVSdkConfiguration (DEMO/src/main/java/group/ccr/ecv175_android/data/Environment.kt) and set per environment in DEMO/build.gradle.kts:

createEnvironmentGradle(
    name = "…",
    serviceUrl = "…",
    useQueueSocket = false,
    useQueueInfo = true,
    …
)

Backend requirement: the service must expose GET /api/WaitForRoomQueueInterval?rtcId=…. Position and wait time are only filled in when Genesys URS returns queue statistics; otherwise they arrive as null and the UI should fall back to a plain spinner.

Listen for the event

QueueInfoUpdated carries a QueueInfo in objData:

videoCall.eventFlow.collect { event ->
    when (event.type) {
        CallEventTypes.QueueInfoUpdated -> {
            queueInfo = event.objData as? QueueInfo
        }

        CallEventTypes.AgentConnected -> { /* navigate to the call screen */ }

        CallEventTypes.Error,
        CallEventTypes.Hangup,
        CallEventTypes.CallEnded -> { /* leave the queue screen */ }

        else -> {}
    }
}

The last snapshot is also available at any time as videoCall.queueInfo, which is useful when the screen is composed after the first event was already emitted:

var queueInfo by remember { mutableStateOf(videoCall.queueInfo) }

QueueInfo

Member Type Meaning
state String Raw wait state (wait, not found, agent_connected, …). The SDK already acts on it; you normally do not need it.
position Int? Raw position. null if the service has no queue info, -1 if URS reported nothing for the interaction (see below).
ewtSeconds Double? Raw estimated wait time in seconds. Same null / -1 rules.
displayPosition Int? position filtered for UI — null unless it is a real position.
ewtMinutes Int? ewtSeconds rounded up to whole minutes, null when unknown.

Prefer displayPosition and ewtMinutes in the UI.

What -1 means

position = -1 together with ewtSeconds = -1 is the service's "no queue data" answer. It is not a call state — read state (or the SDK's own events) for that. It is returned whenever URS has nothing to report for the interaction, which covers several different situations:

  • the interaction left the queue and is already routed and ringing at an agent — URS no longer tracks it as queued, but the call is perfectly alive
  • the interaction never reached URS, or the service could not resolve its connection id
  • the queue statistics mode configured on the service does not match the queue type, so the URS lookup fails
  • the call really did end on the Genesys side

Because these are indistinguishable from the client, never treat -1 as "the call ended" and never end the call because of it. Termination always arrives through the state field and the SDK's CallEnded / Hangup / Error events.

-1 is also perfectly normal while queued: the log below is a caller who is still waiting, with URS simply not publishing figures for that queue.

Queue info: state=wait position=-1 ewt=-1.0

displayPosition and ewtMinutes both return null for -1, so UI code that uses them needs no special handling.

4. Render it

Queue figures are not guaranteed to arrive, so the demo keeps its original waiting screen and only switches to the position layout once there is something real to show. From DEMO/src/main/java/group/ccr/ecv175_android/ui/QueueScreen.kt:

val info = queueInfo
// Only switch layouts once the backend actually reported something to show.
val hasQueueData = info != null && (info.displayPosition != null || info.ewtMinutes != null)

if (hasQueueData) {
    QueueInfoWaiting(info!!, innerPadding, onCancel = { videoCall.hangup() })
} else {
    PlainWaiting(innerPadding, onCancel = { videoCall.hangup() })   // spinner only
}

Inside the position layout each figure is rendered only when it exists, since a queue can publish a position without an EWT or the other way round:

info.displayPosition?.let { position ->
    Text("$position", fontSize = 64.sp, fontWeight = FontWeight.Bold)
}

info.ewtMinutes?.let { minutes ->
    Text("Estimated wait time about $minutes minutes")
}

Design the screen so it works with no figures at all. The first polls happen before the interaction reaches Genesys, some routing strategies never publish an EWT, and a caller who gets routed mid-wait goes back to -1 (see above). A layout that assumes a number is always present will show an empty box for the whole call on such a queue.

Troubleshooting

Symptom Cause Fix
No QueueInfoUpdated events at all Feature not enabled, or the queue socket is still on Check useQueueInfo = true and useQueueSocket = false — in the configuration, or as per-call overrides set before start()
Events arrive with state=wait but position=-1 ewt=-1.0 for the whole call URS reported nothing for this interaction Check the service log for the urs/call/… request, and whether the queue statistics mode configured on the service matches the queue type. The screen stays on the plain waiting layout, which is the intended fallback.
position / ewtSeconds flip to -1 mid-wait The interaction left the queue — usually routed and now ringing at an agent Expected, and not a call end. Keep waiting for AgentConnected.
Queue never progresses rtcId mismatch or the dial failed Compare the rtcId in the app log with the dialex request on the service

Hold and Resume

When the agent puts the call on hold from the agent desktop, the SDK reports it to your application so you can cover the screen with a "please wait" overlay, and reports the resume so you can take it away again.

No configuration is needed — the events are always emitted.

How it works

Hold is driven entirely by the agent side. The agent desktop sends a control message over the WebRTC data channel, and the SDK turns it into an event:

Agent action Data channel message SDK event
Puts the call on hold HOLD|… CallEventTypes.StartHold
Takes the call back PICKUP|… CallEventTypes.EndHold

There is no client-side API to place a call on hold — the caller cannot hold the agent. Your job is to react to the two events.

Implement the overlay

Three pieces: a flag, two event branches, and a composable rendered on top.

Keep a flag

From DEMO/src/main/java/group/ccr/ecv175_android/ui/CallScreen.kt:

var isHeld by remember { mutableStateOf(false) }

Drive it from the events

videoCall.eventFlow.collect { event ->
    when (event.type) {
        CallEventTypes.StartHold -> isHeld = true
        CallEventTypes.EndHold   -> isHeld = false
        // … other events …
        else -> {}
    }
}

Java, using the lifecycle-aware helper:

videoCall.subscribe(this, event -> {
    if (event.getType() == CallEventTypes.StartHold) {
        showHoldOverlay();
    } else if (event.getType() == CallEventTypes.EndHold) {
        hideHoldOverlay();
    }
});

Render the overlay last

Inside the Box that holds the video views and the call controls, render the overlay after everything else so it paints on top:

// --- OVERLAYS ---
// These are rendered last to appear on top of the UI elements above.
if (isOnHold) {
    HoldOverlay()   // Full screen overlay, zIndex 4
}
@Composable
private fun HoldOverlay() {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Black)
            .zIndex(4f), // Above controls
        contentAlignment = Alignment.Center
    ) {
        Icon(
            imageVector = Icons.Filled.HourglassEmpty,
            contentDescription = "Call on Hold",
            modifier = Modifier.size(64.dp),
            tint = Color.White
        )
    }
}

Things to get right

Make it opaque. The overlay is a solid black Box, not a translucent scrim. The agent side stops sending video for the duration of the hold, so what sits behind the overlay is a dead surface — a frozen last frame or a blank view, depending on the device. An opaque overlay replaces that with a deliberate "please wait" state; a translucent scrim would just show the caller a dimmed frozen picture of the agent.

Render it above the controls. zIndex(4f) puts it over the call control row. In the demo this means the caller cannot hang up while on hold. That is a deliberate choice — if your flow needs an escape hatch, either lower the overlay's zIndex below the controls or place a hangup button inside the overlay itself.

The state is per call. isHeld lives in the call screen's composition, so a call that ends while on hold simply disposes it. There is nothing to reset.

Troubleshooting

Symptom Cause Fix
No StartHold / EndHold events The data channel is not up, or the agent desktop does not send hold messages Check for TextChannelConnected in the log; verify hold works from the agent desktop for other clients
Overlay stays after the agent resumes EndHold handled on a collector that was cancelled Collect the events for as long as the call screen is composed