Skip to content

NFC device retrieval

According to ISO 18013-5 one of the device retrieval methods is via Bluetooth NFC. To accomplish that SDK provides an out of the box implementation for retrieving data using NFC.

Enabling NFC device engagement and data retrieval

To enable NFC device engagement for the NFCTransfer, you need to call the enableDeviceEngagement method on the NFCTransfer object. The enableDeviceEngagement SHOULD be called on the onCreate method of the activity, since it registers a lifecycle observer to the activity to handle the NFC device engagement.

The device engagement starts on the RESUMED state of the activity and stops on the PAUSED state of the activity.

The following code snippet demonstrates how to engage with the device via NFC, determine if BLE is supported, and send a request to the device using BLE or NFC.

MainActivity.java
package com.example.app;

import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static com.scytales.mvalid.sdk.engagement.DeviceEngagement.DeviceRetrievalMethod.Type.BLE;

import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.scytales.mdl.iso.reader.sdk.app.R;
import com.scytales.mvalid.sdk.FailureType;
import com.scytales.mvalid.sdk.Received;
import com.scytales.mvalid.sdk.data.DeviceResponse;
import com.scytales.mvalid.sdk.data.Request;
import com.scytales.mvalid.sdk.data.RequestBuilder;
import com.scytales.mvalid.sdk.engagement.DeviceEngagement;
import com.scytales.mvalid.sdk.engagement.DeviceEngagementCallback;
import com.scytales.mvalid.sdk.engagement.EngagementReceived;
import com.scytales.mvalid.sdk.retrieval.TransferProgressEvent;
import com.scytales.mvalid.sdk.retrieval.TransferProgressListener;
import com.scytales.mvalid.sdk.retrieval.TransferReceiveCallback;
import com.scytales.mvalid.sdk.retrieval.TransferReceived;
import com.scytales.mvalid.sdk.retrieval.device.BLETransfer;
import com.scytales.mvalid.sdk.retrieval.device.NFCTransfer;
import com.scytales.mvalid.sdk.session.Handover;
import com.scytales.mvalid.sdk.session.SessionData;
import com.scytales.mvalid.sdk.session.SessionManager;
import com.scytales.mvalid.sdk.verify.DeviceVerifierResult;

import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MainActivity extends AppCompatActivity implements DeviceEngagementCallback,
        TransferProgressListener, TransferReceiveCallback {

    private static final String TAG = "MainActivity";
    private static final Map<String, Map<String, Map<String, Boolean>>> docRequests =
            new HashMap<String, Map<String, Map<String, Boolean>>>() {{
                put("org.iso.18013.5.1.mDL", new HashMap<String, Map<String, Boolean>>() {{
                    put("org.iso.18013.5.1", new HashMap<String, Boolean>() {{
                        put("family_name", true);
                        put("given_name", true);
                        put("birth_date", true);
                        put("issue_date", true);
                        put("expiry_date", true);
                    }});
                }});
            }};

    private List<X509Certificate> rootCertificates;

    private NFCTransfer nfcTransfer;
    private BLETransfer bleTransfer;
    private SessionManager sessionManager;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
        // Keep screen on
        getWindow().addFlags(FLAG_KEEP_SCREEN_ON);
        this.rootCertificates =
                new ArrayList<X509Certificate>() {{
                    try {
                        X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance("X.509")
                                .generateCertificate(getResources().openRawResource(R.raw.scytales_root_ca));
                        add(certificate);
                    } catch (CertificateException e) {
                        Log.e(TAG, "onCreate", e);
                        // eat it
                    }
                }};
        try {
            this.bleTransfer = new BLETransfer(this)
                    .setTransferProgressListener(this)
                    .setTransferReceiveCallback(this);
            this.nfcTransfer = new NFCTransfer()
                    .enableDeviceEngagement(this, this)
                    .setTransferProgressListener(this)
                    .setTransferReceiveCallback(this);
        } catch (Exception e) {
            Log.e(TAG, "onCreate", e);
            // Handle exception
        }
    }

    @Override
    public void onEngage(@NonNull Received<EngagementReceived> received) {
        try {
            received.runOnSuccessCatching(engagementReceived -> {
                DeviceEngagement deviceEngagement = engagementReceived.getDeviceEngagement();
                Handover handover = engagementReceived.getHandover();
                // ... use handover to determine the device engagement QR/NFC
                // ... deviceEngagement contains the engagement information to
                // proceed with data transfer

                // create session manager
                this.sessionManager = new SessionManager(deviceEngagement, handover);

                Request request = new RequestBuilder()
                        .setDocRequests(docRequests)
                        .setSessionManager(this.sessionManager)
                        .build();

                boolean isBLESupported = deviceEngagement
                        .getDeviceRetrievalMethods()
                        .stream()
                        .anyMatch(method -> method.getType().equals(BLE));


                if (isBLESupported) {
                    // suspend NFC and handover to BLE
                    this.nfcTransfer.suspendDeviceEngagement();
                    // handover to BLE
                    bleTransfer.forDeviceEngagement(deviceEngagement)
                            .send(request);
                } else {
                    // continue with NFC
                    this.nfcTransfer.send(request);
                }
            });

            received.runOnFailureCatching(failure -> {
                Log.e(TAG, failure.toString());
                FailureType type = failure.getType();
                // handle failed device engagement
                // Handle throwable
            });
        } catch (Exception e) {
            Log.e(TAG, "onEngage", e);
            // handle exception
        }
    }

    @Override
    public void onProgressEvent(@NonNull TransferProgressEvent transferProgressEvent) {
        Log.i(TAG, "Transfer progress: " + transferProgressEvent);
    }

    @Override
    public void onReceive(@NonNull Received<TransferReceived> received) {
        // resume NFC device engagement and stop BLE
        this.nfcTransfer.resumeDeviceEngagement();
        this.bleTransfer.stop();
        try {
            received.runOnSuccessCatching(transferReceived -> {
                SessionData sessionData = sessionManager.decryptResponse(transferReceived.getReceivedBytes());
                DeviceResponse response = DeviceResponse.fromBytes(sessionData.getData());
                List<DeviceVerifierResult> verifierResult = sessionManager
                        .getVerifier(rootCertificates)
                        .verify(response.getRawBytes());

                Log.i(TAG, "Device response: " + response);
                Log.i(TAG, "Verifier result: " + verifierResult);
            });

            received.runOnFailureCatching(failure -> {
                Log.e(TAG, failure.toString());
                FailureType type = failure.getType();
                // handle failed data transfer
                // Handle throwable
            });
        } catch (Exception e) {
            Log.e(TAG, "onReceive", e);
            // handle exception
        }
    }
}