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.
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
}
}
}