Skip to content

NFC device retrieval with HID Omnikey USB reader

Scytales mValid SDK provides an out of the box implementation for retrieving data using NFC with the HID Omnikey USB reader.

Requirements

First, download the Android Driver from https://www.hidglobal.com/drivers/32404 and add the .aar library file to your project dependencies. For example:

dependencies {
    implementation "android.smartcardio:hid:2.2@aar"
}

Note: Currently, SDK is tested with the HID Omnikey android driver version 2.2

Then, create the file device_filter.xml in the res/xml directory with the following content:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- HID -->
    <usb-device vendor-id="1899" />
</resources>

To use the HID Omnikey USB reader, you need to have the following intent filter for the activity that will handle the NFC transfer, in the AndroidManifest.xml file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.app">
    <application android:name=".App">

        <!-- Other activities -->
        <!-- ... -->

        <!-- Acticity for HID NFC tranfer -->
        <activity android:name=".NFCWithHIDActivity" android:exported="false">

            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>

            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />

        </activity>

        <!-- ... -->
    </application>
</manifest>

Enabling NFC device engagement and data retrieval

To enable NFC device engagement for the NFCTransfer, you need to call the enableDeviceEngagement method on the HIDTransfer 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 Omnikey HID reader, 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.retrieval.device.nfc.HIDTransfer;
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 HIDTransfer 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 HIDTransfer()
                    .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
        }
    }
}