Skip to main content
Version: v0.13

Engagement Manager

The MdocEngagementManager is the primary entry point for mDoc operations in the IDK. It manages the complete lifecycle from creating engagements through completing data transfers.

Obtaining the Engagement Manager

The engagement manager is available from the session component:

val engagementManager = session.component.mdocEngagementManager

Key Properties

The engagement manager provides access to engagement instances and events:

// Event hub for UI integration (primary integration point)
val eventHub: MdocEventHub = engagementManager.eventHub

// Shared parameters (BLE UUIDs, ephemeral keys)
val sharedParameters: SharedParameters = engagementManager.sharedParameters

// Engagement instances by type (only ONE per type at a time)
val engagementsByType: StateFlow<Map<EngagementType, EngagementInstance>>

// Direct access to specific engagement types
val qrEngagement: StateFlow<EngagementInstance?> = engagementManager.qrEngagement
val nfcEngagement: StateFlow<EngagementInstance?> = engagementManager.nfcEngagement
val toAppEngagement: StateFlow<EngagementInstance?> = engagementManager.toAppEngagement

// Currently active engagement (ONE at a time)
val activeEngagement: StateFlow<EngagementInstance?> = engagementManager.activeEngagement

// Active transfer instances
val transferInstances: StateFlow<Map<Uuid, TransferInstance>>

Engagement Types

The manager supports three engagement types:

TypeDescription
QRISO 18013-5 QR code-based engagement
NFCISO 18013-5 proximity-based engagement
TO_APPISO 18013-7 app-to-app / reverse engagement

Creating TO_APP Engagement

Create a TO_APP engagement from an incoming URI:

// Handle incoming mdoc:// URI
val uri = "mdoc://..." // or "mdoc:" or "mdoc-openid4vp://"

val result = engagementManager.toApp(
mdocUri = uri,
autoStart = true // Automatically start the engagement
)

when (result) {
is IdkResult.Success -> {
val engagementInstance = result.value
// Engagement created successfully
}
is IdkResult.Failure -> {
val error = result.error
// Handle error
}
}

URI Scheme Patterns

The toApp() method supports three URI patterns:

SchemeDescription
mdoc: (opaque)Classic reverse engagement with BLE/NFC
mdoc:// (hierarchical)REST API / Website retrieval
mdoc-openid4vp://OID4VP with OAuth 2.0

Working with EngagementInstance

The EngagementInstance represents a single engagement session:

val engagement: EngagementInstance = engagementManager.qrEngagement.value!!

// Access engagement properties
val id: Uuid = engagement.id
val data: EngagementData = engagement.data
val isActive: StateFlow<Boolean> = engagement.isActive

// Get engagement URI for QR code
val uri: String = engagement.getEngagementUri()

// Get ephemeral key
val ephemeralKey: CoseKeyType = engagement.getEphemeralKey()

// Get device engagement object
val deviceEngagement: CborEncodedItem<DeviceEngagement> = engagement.getDeviceEngagement()

// Get current state
val state: MdocEngagementStateType = engagement.getCurrentState()

// Get available retrieval methods
val retrievalMethods: Set<DeviceRetrievalMethod> = engagement.getRetrievalMethods()

// Check if transfer is initialized
val isTransferReady: Boolean = engagement.isTransferInitialized()

// Subscribe to events
engagement.events.collect { event ->
// Handle engagement events
}

Starting a Transfer

Once an engagement is established, start the transfer to get a TransferManager:

// Start engagement and get transfer manager
val transferManager: TransferManager = engagement.start()

// Or using Try interface for result-based error handling
val result = engagement.tryOps().start()

when (result) {
is IdkResult.Success -> {
val transferManager = result.value
// Use transfer manager
}
is IdkResult.Failure -> {
val error = result.error
// Handle error
}
}

Event Hub Integration

The MdocEventHub is the primary integration point for UI:

val eventHub: MdocEventHub = engagementManager.eventHub

// Combined stream of all events
eventHub.allEvents.collect { event ->
when (event) {
is MdocEngagementEvent -> handleEngagementEvent(event)
is MdocRetrievalEvent -> handleRetrievalEvent(event)
}
}

// Or subscribe to specific event types
eventHub.engagementEvents.collect { event ->
val state = event.state
val engagementId = event.engagementId
val isActive = event.isActive
// Handle engagement-specific events
}

eventHub.retrievalEvents.collect { event ->
// Handle transfer/retrieval events
}

// Get recent events for debugging
val recentEvents: List<SequencedEvent> = eventHub.getRecentEvents(count = 10)

// Get current state by engagement ID
val statesByEngagement: StateFlow<Map<Uuid, MdocEngagementState>> =
eventHub.getCurrentStateByEngagement()

Closing Engagements

Close engagements when done:

// Close specific engagement types
engagementManager.closeNfcEngagement()
engagementManager.closeQrEngagement()
engagementManager.closeToAppEngagement()

// Close by instance
engagementManager.closeEngagementByInstance(engagementInstance)

// Close by ID
engagementManager.closeEngagementById(engagementId)

// Close all engagements
engagementManager.closeAll()

Auto Restart Configuration

Enable automatic cleanup and NFC restart:

// Enable auto-restart for NFC engagements
engagementManager.enableAutoRestart(backgroundNfcConfig)

Shared Parameters

The SharedParameters manage parameters shared across engagements:

val sharedParams: SharedParameters = engagementManager.sharedParameters

// Access BLE UUIDs
val centralClientUuid: StateFlow<Uuid> = sharedParams.bleCentralClientUuid
val peripheralServerUuid: StateFlow<Uuid> = sharedParams.blePeripheralServerUuid

// Access shared ephemeral key
val ephemeralKey: StateFlow<CoseKeyType> = sharedParams.ephemeralKey

// Regenerate parameters for new session
sharedParams.regenerate()

// Use same UUID for both BLE modes
sharedParams.useSameUuidForBothModes()

Engagement Lifecycle States

Engagements progress through these states:

StateDescription
INITIALIZINGEngagement is being set up
CONNECTINGEstablishing transport connection
CONNECTEDTransport connected, ready for transfer
TRANSFERRINGData exchange in progress
COMPLETEDTransfer completed successfully
ERRORAn error occurred

Complete Example

Here's a complete example of handling an incoming mDoc URI:

class MdocPresentationHandler(
private val engagementManager: MdocEngagementManager,
private val documentProvider: DocumentProvider
) {
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())

fun handleIncomingUri(uri: String) {
scope.launch {
// Create engagement from URI
val result = engagementManager.toApp(mdocUri = uri, autoStart = false)

when (result) {
is IdkResult.Success -> {
val engagement = result.value
observeEngagement(engagement)

// Start the engagement
val transferManager = engagement.start()
handleTransfer(transferManager)
}
is IdkResult.Failure -> {
handleError(result.error)
}
}
}
}

private suspend fun observeEngagement(engagement: EngagementInstance) {
engagement.events.collect { event ->
when (event.state) {
is MdocEngagementState.Connected -> {
updateUi("Connected to verifier")
}
is MdocEngagementState.Error -> {
val error = (event.state as MdocEngagementState.Error).error
handleError(error)
}
}
}
}

private suspend fun handleTransfer(transferManager: TransferManager) {
// Receive device request
val deviceRequest = transferManager.receiveDeviceRequest()

// Show consent UI to user
val userApproved = showConsentDialog(deviceRequest)

if (userApproved) {
// Create and send response
val deviceResponse = transferManager.createResponse(
deviceRequest = deviceRequest,
documentProvider = documentProvider
)
transferManager.sendDeviceResponse(deviceResponse)
} else {
// Send error response
val errorResponse = DeviceResponse.Builder()
.withStatus(DeviceResponseStatus(20u)) // User cancelled
.build()
transferManager.sendDeviceResponse(errorResponse)
}
}

fun cleanup() {
engagementManager.closeAll()
scope.cancel()
}
}