GuidesAndroid mDocs Holder SDK🎓 Proximity presentation

Learn how to build an Android application that can present an mDoc via a proximity workflow

Introduction

In this tutorial we will use the Android native mDoc Holder SDK to build an Android application that can present a claimed mDoc to a verifier that supports proximity verification as per ISO/IEC 18013-5:2021.

Tutorial Workflow

  1. The user launches the application and generates a QR code.
  2. The verifier scans the QR code, connects with the application and requests an mDoc for verification.
  3. The applications displays matching credentials to the user and asks for consent to share them with the verifier.
  4. The verifier receives the application’s response and verifies the provided credential.

The result will look something like this:

Prerequisites

Before we get started, let’s make sure you have everything you need.

Prior knowledge

If you need to get a holding solution up and running quickly with minimal development resources and in-house domain expertise, talk to us about our white-label MATTR GO Hold app which might be a good fit for you.

Assets

As part of your onboarding process you should have been provided with access to the following assets:

  • ZIP file which includes the required library: (holder-*version*.zip).
  • Sample Wallet app: You can use this app for reference as we work through this tutorial.

Development environment

Prerequisite tutorial

  • You must complete the Claim a credential tutorial, build the application and use it to claim the mDoc provided in the tutorial.
  • This application is used as the base for the current tutorial.

Testing devices

As this tutorial implements a proximity presentation workflow, you will need two separate physical mobile devices to test the end-to-end result:

  • Holder device:
    • Supported Android device to run the built application on, setup with:
      • Biometric authentication (Face recognition, fingerprint recognition).
      • Bluetooth access and Bluetooth turned on.
      • Available internet connection.
      • USB debugging enabled.
  • Verifier device:
    • Android/iOS mobile device with an installed verifier application. We recommend downloading and using the MATTR GO Verify example app.
    • Setup with Bluetooth access.

Got everything? Let’s get going!

Tutorial steps

To enable a user to present a stored mDoc to a verifier via a proximity presentation workflow, you will build the following capabilities into your application:

  1. Create and present a QR code to establish a secure connection with the verifier application.
  2. Receive and handle a presentation request from the verifier.
  3. Send a matching mDoc presentation to the verifier.

Create and Present a QR code to establish a secure connection

The first capability we need to build in is for your application to be able to establish a secure communication channel between the verifier and holder devices. As defined in ISO/IEC 18130-5:2021, a proximity presentation workflow is always initiated by the holder (wallet user), who must create a QR code for the verifier to scan in order to initiate the device engagement phase.

To achieve this, we will first create a new screen where the user can present a QR code with the information required to establish a secure communication channel.

  1. In your Claim a tutorial application project, create a new file named PresentationQrScreen.kt and add the following code:

    PresentationQrScreen.kt
    import android.app.Activity
    import android.graphics.Bitmap
    import android.graphics.Color
    import android.widget.Toast
    import androidx.compose.foundation.Image
    import androidx.compose.foundation.layout.Box
    import androidx.compose.foundation.layout.aspectRatio
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.LaunchedEffect
    import androidx.compose.runtime.getValue
    import androidx.compose.runtime.mutableStateOf
    import androidx.compose.runtime.remember
    import androidx.compose.runtime.setValue
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.graphics.asImageBitmap
    import androidx.compose.ui.layout.onSizeChanged
    import androidx.compose.ui.unit.IntSize
    import androidx.navigation.NavController
    import com.google.zxing.BarcodeFormat
    import com.google.zxing.EncodeHintType
    import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
    import com.journeyapps.barcodescanner.BarcodeEncoder
    import global.mattr.mobilecredential.holder.MobileCredentialHolder
    import global.mattr.mobilecredential.holder.ProximityPresentationSession
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.withContext
     
    @Composable
    fun PresentationQrScreen(activity: Activity, navController: NavController) {
        var containerSize by remember { mutableStateOf(IntSize.Zero) }
        var session: ProximityPresentationSession? by remember { mutableStateOf(null) }
        var qrCode: Bitmap? by remember { mutableStateOf(null) }
     
        LaunchedEffect(Unit) {
            // Step 1.4: Create a session
        }
     
        LaunchedEffect(session, containerSize) {
            // Step 1.6: Generate a QR code
        }
     
        Box(
            modifier = Modifier
                .aspectRatio(1f)
                .onSizeChanged { containerSize = it }
        ) {
            qrCode?.let {
                Image(
                    bitmap = it.asImageBitmap(),
                    contentDescription = "A QR Code",
                    modifier = Modifier.fillMaxSize()
                )
            }
        }
    }
     
    // Step 1.5: Create function to generate QR Code from String
     

We will copy and paste different code snippets into specific locations in this codebase to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step.

We recommend copying and pasting the comment text (e.g. // Proximity Presentation - Step 1.2: Add "QR" screen call) to easily locate it in the code.

  1. Return to your MainActivity file and add the following code under the // Proximity Presentation - Step 1.2: Add "QR Presentation" screen call comment to connect the created composable to the navigation graph:

    MainActivity.kt
    PresentationQrScreen(this@MainActivity, navController)
  2. Still in the MainActivity file file, add the following code under the // Proximity Presentation - Step 1.3: Add button for starting the credentials presentation workflow comment to add a button that navigates the user to the PresentationQrScreen:

    MainActivity.kt
    Button(onClick = { navController.navigate("presentationQr") }, Modifier.fillMaxWidth()) {
        Text("Present Credentials")
    }
  3. Open the PresentationQrScreen.kt file and add the following code under the // Step: 1.4 Create a session comment to call the createProximityPresentationSession function when the screen is loaded:

    PresentationQrScreen.kt
    session = MobileCredentialHolder.getInstance().createProximityPresentationSession(
        activity,
        onRequestReceived = { _, requests, e ->
            // Step 2.2: Handle the presentation request
        }
    )

Now, when the PresentationQrScreen is loaded, the application will call the SDK’s createProximityPresentationSession function, which returns a ProximityPresentationSession instance that includes a deviceEngagement string in base64 format:

"mdoc:owBjMS4wAYIB2BhYS6QBAiABIVgghaBYJe7KSqcEolhmnIJaYJ2AIevkKbEy5xP7tkwlqAwiWCAMGCGe6uFI2hKeghb59h_K4hPV-Ldq6vnaxsRiySMH9gKBgwIBowD0AfULUKRoj0ZH60Qco-m0k97qRSQ"

This deviceEngagement string is always prefixed with mdoc: and contains the information required to establish a secure connection between the holder and verifier devices, including:

  • Wireless communication protocols supported by the holder.
  • How the verifier can connect with the holder.
  • Ephemeral public key which is unique to the current session.
  • Additional feature negotiations.

Your application needs to convert this deviceEngagement string into a QR code and display it in the PresentationQrScreen for the verifier to scan.

  1. Add the following code under the // Step 1.5: Create function to generate QR Code from String comment to add a function that converts a String to a QR code rendered as a Bitmap image:

    PresentationQrScreen.kt
    private fun String.toQrCode(size: IntSize): Bitmap? {
        if (this.isEmpty() || size == IntSize.Zero) return null
     
        val (width, height) = size
        return Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565).apply {
            val hints = mapOf(EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.M)
            val encoded = BarcodeEncoder()
                .encode(this@toQrCode, BarcodeFormat.QR_CODE, width, height, hints)
     
            for (x in 0 until width) {
                for (y in 0 until height) {
                    setPixel(x, y, if (encoded[x, y]) Color.BLACK else Color.WHITE)
                }
            }
        }
    }

To generate the QR code, we need the ProximityPresentationSession to be established by the SDK and the container size to be calculated.

  1. Add the following code under the // Step 1.6: Generate a QR code comment to call the new toQrCode function and generate the QR code when the session or containerSize state changes:

    PresentationQrScreen.kt
    qrCode = session?.deviceEngagement?.toQrCode(containerSize)
  2. Run the app, and select the Present Credentials button. You should see a result similar to the following:

  • As the user selects the Present Credentials button, they are navigated to the new PresentationQrScreen, and the session is being created.
  • Once the session is created, the application generates and displays a QR code that can be scanned by a verifier device to establish a secure proximity communication channel over Bluetooth.

Tutorial Workflow

Once the QR code is displayed, the ProximityPresentationSession enters a listening state, ready to establish a Bluetooth connection with a verifier application that scans the QR Code.

When a verifier application scans the QR code, the devices will automatically exchange public keys to establish a secure communication channel, enabling the verifier to send a presentation request, which details what information is required and for what purpose.

Handle the presentation request

Tutorial Workflow

The createProximityPresentationSession function can handle three types of events:

  • onConnected: When a secure connection is established.
  • onSessionTerminated: When a secure connection is terminated for whatever reason.
  • onRequestReceived: When a presentation request is received from the verifier.

onConnected and onSessionTerminated are optional events and will not be implemented in this tutorial. Check out our SDK Docs for a complete description of these events and how you can handle them.

When the SDK receives a presentation request from a verifier, an onRequestReceived event is triggered. The SDK then checks its credential storage for any credentials that match the information defined in this request.

The application then needs to:

  • Present these matching credentials to the user.
  • Present what claims will be shared with the verifier.
  • Provide a UI element for the user to consent sharing this information with the verifier.
  1. In your MainActivity file, add the following code under the // Step 2.1: Add proximity presentation request variable comment to create a new variable that will hold the presentation request information and share it between the screens:

    MainActivity.kt
    var proximityPresentationRequest: ProximityPresentationSession.CredentialRequestInfo? = null
  2. Open the PresentationQrScreen.kt file and add the following code under the // Step 2.2: Handle the presentation request comment to navigate to a new screen when an onRequestReceived event is received.

    PresentationQrScreen.kt
    if (e == null && !requests.isNullOrEmpty()) {
        // Using only the first request for simplicity
        SharedData.proximityPresentationRequest = requests.first()
        withContext(Dispatchers.Main) {
            navController.navigate("presentationSelectCredentials")
        }
    } else {
        val msg = "Error while retrieving the request"
        withContext(Dispatchers.Main) {
            Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show()
        }
    }
  3. Create a new file named PresentationSelectCredentialsScreen.kt and add the following code to create the new screen which displays matching credentials and enables the user to select which credential to share:

    PresentationSelectCredentialsScreen.kt
    import android.app.Activity
    import androidx.compose.foundation.clickable
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.Row
    import androidx.compose.foundation.layout.Spacer
    import androidx.compose.foundation.layout.fillMaxWidth
    import androidx.compose.foundation.layout.padding
    import androidx.compose.foundation.rememberScrollState
    import androidx.compose.foundation.verticalScroll
    import androidx.compose.material3.Button
    import androidx.compose.material3.MaterialTheme
    import androidx.compose.material3.RadioButton
    import androidx.compose.material3.Text
    import androidx.compose.material3.TextButton
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.getValue
    import androidx.compose.runtime.mutableStateOf
    import androidx.compose.runtime.remember
    import androidx.compose.runtime.rememberCoroutineScope
    import androidx.compose.runtime.setValue
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.unit.dp
    import global.mattr.mobilecredential.common.deviceretrieval.devicerequest.DataElements
    import global.mattr.mobilecredential.common.deviceretrieval.deviceresponse.NameSpace
    import global.mattr.mobilecredential.common.dto.MobileCredential
    import global.mattr.mobilecredential.holder.MobileCredentialHolder
    import global.mattr.mobilecredential.holder.ProximityPresentationSession
    import kotlinx.coroutines.launch
     
    @Composable
    fun PresentationSelectCredentialsScreen(activity: Activity) {
        val request = SharedData.proximityPresentationRequest ?: return
        if (request.matchedCredentials.isEmpty()) return
     
        var presentationRequest by remember { mutableStateOf(request) }
        var selectedCredentialId by remember {
            mutableStateOf(presentationRequest.matchedCredentials.first().id)
        }
        val coroutineScope = rememberCoroutineScope()
     
        Column(Modifier.verticalScroll(rememberScrollState())) {
            Text("REQUESTED INFORMATION", style = MaterialTheme.typography.titleLarge)
            Document(
                presentationRequest.request.docType,
                presentationRequest.request.nameSpaces.value.toUi()
            )
            Spacer(Modifier.padding(12.dp))
     
            Text("MATCHING CREDENTIALS", style = MaterialTheme.typography.titleLarge)
            presentationRequest.matchedCredentials.forEach { matchedCredential ->
            // Step 2.5: Display matching credentials and claims
            }
            // Step 3.2: Send response
        }
    }
     
    // Step 2.4: Create function to add values to claims
     
     
    private fun Map<NameSpace, DataElements>.toUi() = mapValues { (_, dataElements) ->
        dataElements.value.keys.toSet()
    }
     
    // Step 3.1: Create function to send the credential response

The application retrieves information from the SDK’s ProximityPresentationSession object and displays it to the user:

  • Credentials and claims included in the verification request are retrieved from the docType and nameSpaces.value properties and displayed in the “REQUESTED INFORMATION” area.
  • Available credentials that match the requested information are retrieved from the matchedCredentials property and displayed in the “MATCHING CREDENTIALS” area.
  1. Add the following code under the // Step 2.4: Create function to add values to claims comment to create a new function that will enable displaying the values of the claims the user is about to share.

    PresentationSelectCredentialsScreen.kt
    private fun ProximityPresentationSession.CredentialRequestInfo.withClaimValues(
        credentialWithValues: MobileCredential
    ): ProximityPresentationSession.CredentialRequestInfo = copy(
        matchedCredentials = matchedCredentials.map { credential ->
            if (credential.id == credentialWithValues.id) {
                credential.copy(
                    claims = credential.claims.mapValues { (namespace, claims) ->
                        claims.map { claim ->
                            val claimValue = credentialWithValues.claims[namespace]?.get(claim)
                            claimValue?.let { "$claim: ${it.toUiString()}" } ?: claim
                        }.toSet()
                    }
                )
            } else {
                credential
            }
        }
    )
  2. Add the following code under the // Step 2.5: Display matching credentials and claims comment to display to the user what credentials and claims they are about to share with the verifier, as well as a button that enables the user to display the value of those claims as well (we do not show this by default as claims are likely to include PII):

    PresentationSelectCredentialsScreen.kt
     Row(
         Modifier
             .fillMaxWidth()
             .clickable { selectedCredentialId = matchedCredential.id }) {
     
         Column {
             RadioButton(
                 selected = matchedCredential.id == selectedCredentialId,
                 onClick = { selectedCredentialId = matchedCredential.id }
             )
             TextButton(
                 onClick = {
                     val credentialWithValues = MobileCredentialHolder.getInstance()
                         .getCredential(matchedCredential.id, skipStatusCheck = true)
     
                     presentationRequest =
                         presentationRequest.withClaimValues(credentialWithValues)
                 }
             ) { Text("Show\nValues") }
         }
     
         Document(
             matchedCredential.docType,
             matchedCredential.claims,
             Modifier.weight(1f)
         )
     }
  3. Back in the MainActivity file, add the following code under the // Proximity Presentation - Step 2.6: Add "Select Credential" screen call comment to connect the created composable to the navigation graph:

    MainActivity.kt
    PresentationSelectCredentialsScreen(this@MainActivity)
  4. Run the app, and select the Present Credentials button. Next, use your testing verifier app to scan the presented QR code and send a presentation request. You should see a result similar to the following:

  • As the user selects the Present Credentials button, they are navigated to the new PresentCredentialsView where the application generates and displays a QR code.
  • When a compliant verifier app scans the QR code, a secure communication channel is established via Bluetooth.
  • The verifier then sends a presentation request, which is displayed to the user alongside any credentials they have stored that match the request.

Send a presentation response

Tutorial Workflow

The next capability we need to build is for the application to send a presentation response upon receiving consent from the holder to share information with the verifier.

Once the user provides the consent by selecting the document they want to share, the application should call the SDK’s sendResponse function to share the information with the verifier as a presentation response.

  1. Open the PresentationSelectCredentialsScreen.kt file and add the following code under the // Step 3.1: Create function to send the credential response comment to create a new function that calls the SDK’s sendResponse method.

    PresentationSelectCredentialsScreen.kt
    private suspend fun sendResponse(credentialId: String, activity: Activity) {
        MobileCredentialHolder.getInstance()
            .getCurrentProximityPresentationSession()
            ?.sendResponse(listOf(credentialId), activity)
    }

    The sendResponse function signs the presentation response with the user’s device private key (to prove Device authentication) and shares it as an encoded CBOR file.

  2. Add the following code under the // Step 3.2: Send response to call the created sendResponse function when the user selects the “Send Response” button:

    PresentationSelectCredentialsScreen.kt
    Button(
        onClick = {
            coroutineScope.launch { sendResponse(selectedCredentialId, activity) }
        },
        Modifier.fillMaxWidth()
    ) { Text("Send Response") }
  3. Run the app and perform the following:

    1. Select the Present Credentials button.
    2. Use your testing verifier app to scan the present QR code and send a presentation request.
    3. Back on the holder device, select the matching credential to share.
    4. Select Send Response.

    You should see a result similar to the following:

As the user selects the credential to share, the verifier app will receive the presentation response, verify any incoming credentials and display the verification results.

Congratulations, you have now completed this tutorial, and should have a working application that can claim an mDoc using an OID4VCI workflow, and present it to a verifier for proximity verification via Bluetooth.

Summary

You have just used the Android mDoc holder SDK to built an application that can present a claimed mDoc to a verifier that supports proximity verification as per ISO/IEC 18013-5:2021.

Tutorial Workflow

This was achieved by building the following capabilities into the application:

  • Generating a QR code for the verifier to scan and establish a secure communication channel.
  • Receive and handle a presentation request from the verifier.
  • Display matching credentials to the user and ask for consent to share them with the verifier.
  • Send matching credentials to the verifier as a presentation response.

What’s next?

  • You can check out the Android mDoc holder SDK Docs to learn more about available functions and classes.