VFace Android Integration
This guide covers the VFace biometric and its use in the Veridium SDK. It demonstrates the necessary steps for enrolling, authenticating, template storage options, and UI customisation.
Requirements
VFace requires a minimum Android API level of 16 and a front facing camera with a minimum resolution of approximately 1 megapixel (e.g. 720x1280).
Modules
veridium-vface.aar
- Provides the core logic for the biometric and the Activity from which the biometric is launched.veridium-vface-ui.aar
- A UI for portrait mode, self capture, as used in the Veridium Authenticator app.
In addition to the VFace modules also include the following dependencies from the Veridium SDK:
veridium-analytics-release
veridium-core-release
veridium-sdk-release
veridium-secure-data-release
veridium-support-release
Base Activity
The VFace base activity,VFaceBiometricsActivity
, is contained in the veridium-vface
module. Create an Android activity within your own application that extends this base activity. Here you can customise some aspects of the biometric, such as the UI fragment to use, or the biometric storage location.
package my.app.package;
import com.veridiumid.sdk.vface.VFaceBiometricsActivity;
import com.veridiumid.sdk.vface.VFaceFragment;
public class MyVFaceActivity extends VFaceBiometricsActivity
{
// Specify the UI fragment to use
@Override protected VFaceFragment fragmentToShow()
{
return new DefaultVFaceFragment();
}
}
Meta-data configuration
To configure the SDK use the built-in biometric AndroidManifest.xml
meta-data mechanism. Add a manifest entry for your VFace activity extending VFaceBiometricsActivity
.
AndroidManifest.xml
allows the insertion of <meta-data>
tags that can be parsed/extracted at runtime. The SDK extracts each Activity's meta-data with the property android:name="com.veridiumid.sdk.component.config"
Below is a sample configuration for the VFace biometric, identified by the UID VFACE
:
<activity
android:name="my.app.package.MyVFaceActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<meta-data
android:name="com.veridiumid.sdk.component.config"
android:value="uid=VFACE, optional=false, validator=com.veridiumid.sdk.vface.VFaceValidator" />
</activity>
The default configuration string format is comma-separated key=value pairs.
android:value="parameterName=parameterValue, anotherParameterName=anotherParameterValue"
The validator
value is an IBiometricValidator
that detects if the biometric component has all its dependencies met to function properly on the target device.
In full, the supported parameters are:
uid=stringValue
- unique biometric identifier, used to resolve desired biometric components. Useuid=VFace
for VFace.optional=booleanStringValue
-true|false
indicates that enrolment of this component could be skipped.validatorClass=stringClassName
- the full class name of an IBiometricsValidator implementation. Use the provided classcom.veridiumid.sdk.vface.VFaceValidator
Permissions
Required camera permissions:
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
Required connectivity permissions:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Initalization and Licensing
Each biometric requires its own licence (Please contact your Veridium sales representative for the purchase of licence keys).
Provide your licence to the VeridiumSDKVFaceInitializer
during SDK initialisation, for example:
try {
String vfaceLicense = "my_licence_key";
VeridiumSDK.init(this,
new DefaultVeridiumSDKModelFactory(this),
new VeridiumSDKDataInitializer(),
new VeridiumSDKVFaceInitializer(vfaceLicense));
} catch (SDKInitializationException e) {
Log.w("App", "Failed to initialize Veridium SDK", e);
}
Liveness
Directed Liveness
Directed Liveness is an active liveness system which instructs the user, with text instructions and visual prompts, to turn their head in a series of randomly chosen directions until the system is satisfied that it is seeing a live user.
The user has a limited amount of time to complete each motion. Failure to complete in time will result in a liveness failure.
Failure to pass liveness halts the capture process and the SDK returns an IVeridiumSDK#LIVENESS_FAILED
result.
Liveness is compulsory during enrolment. Liveness during authentication is optional. To set liveness on/off override the VFaceBiometricsActivity
method isUsingVFaceLiveness()
, for example:
@Override
protected boolean isUsingVFaceLiveness() {
return true;
}
Liveness Factor
The robustness of VFace to a presentation attack is defined by the liveness factor; defined by an integer value between 0 and 99. Higher values trade easy-of-use for higher security. The default value is 50.
The factor controlls the number of motions the user must perform and how strictly they must be followed, as follows.
Value | Name | Description | Motions Required |
---|---|---|---|
0 | OFF | No liveness is applied | na |
1 - 24 | Low | Lowest friction use | 2 |
25 - 74 | Medium | Prioritises easy-of-use | Between 2 and 3 |
75 - 99 | High | Maximum security | Between 3 and 4 |
To set the liveness factor, override the livenessFactor()
method of VFaceBiometricsActivity
:
@Override
protected int livenessFactor() {
return 75;
}
Enrol And Authentication
Invoking an Operation
Start an operation by invoking the corresponding IVeridiumSDK
interface methods:
IVeridiumSDK#enroll(String[] biometricUids)
Generate enrolment templates
String[] biometricUids
- biometric unique identifiers, as registered by the suppliedIConfiguration#detectComponents()
implementation.
IVeridiumSDK#authenticate(String[] biometricUids)
String[] biometricUids
- biometric unique identifiers, as registered by the suppliedIConfiguration#detectComponents()
implementation.
These methods return an Intent
object which can be registered for launching using a ActivityResultLauncher
, and the result caught in a ActivityResultCallback
. This can include requesting the required permissions for the Intent
. For example:
ActivityResultLauncher<Intent> VFaceLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
// ActivityResultCallback, handle the result here
});
// Example launching an Enrol intent with permissions
private final ActivityResultLauncher<String> enrolWithPermission =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted && mBiometricSDK != null) {
Intent enrollIntent = mBiometricSDK.enroll(VFaceInterface.UID);
VFaceLauncher.launch(enrollIntent);
}
});
, where VFaceInterface.UID
provides the biometric UID string ("VFACE").
[Note: the SDK can enrol multiple biometrics simultaneously, Intent VIDIntent = VeridiumSDK.getSingleton().enrol(FourFInterface.UID, VFaceInterface.UID);
]
[Note Previously on Android, starting an operation intent required the parent activity to call startActivityForResult();
. This method is now deprecated as of Android API 28.]
Handling An Operational Result
A operation result is received in a ActivityResultCallback
as described in Invoking an Operation.
The result object contains:
resultCode
contains the biometric operation result.data
Intent containing biometric data and additional information, such as error messages.
The resultCode may be any of the following:
RESULT_OK
Operation successRESULT_CANCELED
User canceledRESULT_FAILED
Operation failedRESULT_ERROR
An error was encountered
To process the result, and access biometric data, use a BiometricResultsParser
. Implement the IBiometricResultsHandler
interface to customise actions and pass it to the BiometricResultsParser
, along with the resultCode
and intent data
. Results are received for all biometrics that were run:
IBiometricResultsHandler customResultHandler = new IBiometricResultsHandler() {
@Override
public void handleSuccess(Map<String, byte[][]> results) {
// handle template data here
}
@Override
public void handleFailure() {
}
@Override
public void handleCancellation() {
}
@Override
public void handleError(String message) {
}
};
ActivityResultLauncher<Intent> VFaceLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
// ActivityResultCallback, handle the result here
Intent data = result.getData();
int resultCode = result.getResultCode();
BiometricResultsParser.parse(resultCode, data, resultHandler);
});
Typically, the SDK handles template storage internally without the need to access biometric data. (However, this can be customised, see Data Storage Customization).
Access the template data for VFace using its UID, for example a handleSuccess()
implementation:
public void handleSuccess(Map<String, byte[][]> results) {
// handle template data here
byte[] template = null;
for (Map.Entry<String, byte[][]> entry : results.entrySet()) {
String bio_key = entry.getKey();
byte[][] data = entry.getValue();
if(bio_key.equals(VFaceInterface.UID)){
// template data is contained with the first element
template = data[0];
}
}
}
Match Customization and Auth Template Access
VFace accommodates full customization of the match process by overriding the protected method boolean matchAuthentication(BiometricsResult<?> result)
of VFaceBiometricsActivity
. This is called once a template is successfully extracted and provides access to the authentication template. It should return the result of a match (or the result of any processing you wish to perform on the auth template).
Use this override to capture the authentication template (stored in element 0) for later processing. For example,
public class MyVFaceActivity extends VFaceBiometricsActivity {
@Override
protected boolean matchAuthentication(BiometricsResult<?> result) throws IOException {
// Access the auth template and convert to Base64, for example,
// to send to a remote service
String auth_template_base64 = Base64.encodeToString(result.getOutput(0), Base64.NO_WRAP);
return true;
}
}
Returning true will propagate to a success result for the capture, false a failure. If no template could be extracted, for example if the user cancels, this method is not called.
Data Storage Customization
The biometric default activity VFaceBiometricsActivity
contains the following overridable method:
@Override
protected IKVStore openStorage() {
}
This returns a Key-Value Storage object, IKVStore
. The SDK provides two storage types:
InMemoryKVStore
Data stored in memory only (data is lost on application exit and never written to disk)SecureKVStore
Data is written to secure preferences. This type should be used via the providedcom.veridiumid.sdk.defaultdata.DataSorage
class.
For example, to have data written only to memory, and accessed externally, override the openStorage()
method:
private static InMemoryKVStore myMemoryKVStore = new InMemoryKVStore();
@Override
protected IKVStore openStorage() {
return myMemoryKVStore;
}
To create a secure preferences storage object, for example to create multiple enrolments for multiple users, use the DataStorage
class with your own keys:
Image Import (Template From Image)#
VFace templates can be generated from an image containing a face, and used for either enrolment or authentication. Note, one of either the enrol or authentication templates must be from a live capture to perform a match. The image will be assessed for quality as follows:
Frontal facing
Fully within the image bounds
Adequate lighting
Eyes horizontal and level
Minimum eye separation of 38 pixels
Image to Template
A template can be created from an Android Bitmap image using the following public static method:
Pair<Integer, byte[]> VFaceBiometricsActivity.templateFromBitmap(Bitmap image, int role_type)
,where image is a Bitmap
object, and role_type
is a integer flag to generate either an enrol or authentication template where Enrol = 1, Auth = 2. Returns a Pair containing the template as byte[]
and a success code mapping to the following:
Code | Description |
---|---|
0x00000 | Success |
0x00001 | Library not initted |
0x10001 | Failed to get info from Bitmap image |
0x10002 | Bitmap wasn't RGBA8888 |
0x10003 | Locking address of Bitmap pixels failed |
0x20001 | No matching purpose specified |
0x20002 | Can't be interactive |
0x20003 | Can't use liveness |
0x20004 | No face found in image |
0x20005 | Failed quality check |
0x20006 | Function unsupported on platform |
0x20007 | Not initted |
0x20008 | Image extract threw exception |
For example, to create an enrolment template:
@Nullable
private byte[] templateFromImage(){
Bitmap enrol_image = BitmapFactory.decodeResource(getResources(),
R.drawable.subject_001);
Pair<Integer, byte[]> result = CustomVFaceActivity.templateFromBitmap(enrol_image, 1);
final int result_code = result.first;
if (result_code == 0) {
// Success, run auth using this template
return result.second;
} else {
ToastHelper.showMessage(MainActivity.this, "Failed to make template: " + result_code);
return null;
}
}
Authentication With an Image
A template can be used in preference to other stored templates by overriding VFaceBiometricsActivity.retrieveEnrollment()
. For example, return the template generated by templateFromBitmap()
:
public class MyVFaceActivity extends VFaceBiometricsActivity
{
static byte[] template = null; // set as output from templateFromImage()
@Nullable
@Override
protected byte[] retrieveEnrollment() throws IOException {
if(template == null){
// Use the stored enrolment template
return super.retrieveEnrollment();
}
return template;
}
}
Then invoke authentication using IVeridiumSDK#authenticate(String[] biometricUids)
as previously described.
User Interface
Veridium provides a single VFace UI in the module veridium-vface-ui
.
The UI is implemented as a fragment which is displayed full screen. It is composed of a main capture screen and an optional help screen (shown prior to biometric capture).
Design your own fragment for display by extending VFaceFragment
, overriding its methods, and providing your own layout.
Specifying a UI Fragment
To use the desired UI fragment override the fragmentToShow()
method of your VFace activity. For example, to use a custom VFace UI:
package my.app.package;
import com.veridiumid.sdk.vface.VFaceBiometricsActivity;
import com.myapp.CustomVFaceFragment;
public class CustomVFaceActivity extends VFaceBiometricsActivity
{
// Specify the UI fragment to use
@Override protected VFaceFragment fragmentToShow()
{
return new CustomVFaceFragment();
}
}
Designing a Custom UI Fragment
Create a new module and add a class that extends
VFaceFragment
.Import
OnVFragmentReadyListener
and overrideonReady()
to receive a listener.Call
listener.onFourFFragmentReady()
to indicate readiness once the fragment has completed initialisation and to advance the capture process.Inflate your custom layout in
OnCreateView()
.
import com.veridiumid.sdk.support.ui.AspectRatioSafeFrameLayout;
import com.veridiumid.sdk.vface.OnVFragmentReadyListener;
import com.veridiumid.sdk.vface.VFaceFragment;
public class CustomVFaceFragment extends VFaceFragment implements SurfaceHolder.Callback{{
private OnVFragmentReadyListener listener;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
return inflater.inflate(R.layout.custom_layout_vface, container, false);
}
@Override
protected void initView(View view) {
super.initView(view);
// Set up fragment
listener.onVFragmentReady();
}
@Override
public void onReady(OnVFragmentReadyListener onVFragmentReadyListener){
listener = onVFragmentReadyListener;
}
}
Instruction Screen
VFace provides a boolean via setShowInstructionScreen()
to indicate the fragment should show instructions before calling listener.onVFragmentReady()
. For example:
@Override
protected void initView(View view) {
super.initView(view);
// Set up fragment ....
if(shouldShowInstructionScreen) {
showInstructionDialog();
}else{
listener.onVFragmentReady();
}
}
@Override
public void setShowInstructionScreen(boolean showInstructionScreen) {
shouldShowInstructionScreen = showInstructionScreen;
}
Requesting to Cancel
Allow the user to interrupt the capture process by calling VFaceFragment.requestCancel();
. For example, a cancel button:
cancelButton = view.findViewById(R.id.button_Cancel);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
requestCancel();
}
});
Camera Preview
VFace controls the camera device internally so integrators need not worry about the camera api and the complexities that come with ensuring device compatibility.
However, preview frames generated by the camera can be shown in your UI by providing an AspectRatioSafeFrameLayout
frame layout in your layout via getPreviewHolder()
. Preview frames from the camera are posted to this frame layout.
private AspectRatioSafeFrameLayout previewHolder;
@Override
protected void initView(View view) {
super.initView(view);
...
previewHolder = view.findViewById(R.id.previewView);
...
}
@Override
public AspectRatioSafeFrameLayout getPreviewHolder() {
return previewHolder;
}
Interacting with VFace
The following additional methods are available for override:
void handleUIInstruction(uiInstruction instruction)
Realtime user instructions. See handleUIInstruction.void dismiss(VFaceFinishReason reason)
. Called at operation completion with a reason. See [dismissing VFace](#dismissing vface).void processingStarted()
. VFace has begun processing a capture.void processingFinished()
. VFace has finished processing a capture.
Handle UI Instructions
VFace requests user actions via handleUIInstruction(uiInstruction instruction)
, where uiInstruction
values are:
Value | Description |
---|---|
VFaceInstructionBlank | The user is already positioned as required. |
VFaceInstructionNoFace | Cannot find a face. |
VFaceInstructionMoveAway | Move away from the camera. |
VFaceInstructionMoveCloser | Move toward the camera. |
VFaceInstructionMoveUp | Move up in the camera view. |
VFaceInstructionMoveDown | Move down in the camera view. |
VFaceInstructionMoveLeft | Move left in the camera view. |
VFaceInstructionMoveRight | Move right in the camera view. |
VFaceInstructionNeedFrontal | Face the camera straight on. |
VFaceInstructionNeedSteady | Stay still as possible. |
VFaceInstructionNeedBrighter | Images from the camera are too bright. |
VFaceInstructionNeedDarker | Images from the camera are too dark. |
VFaceInstructionNeedSteadyFrontal | Face the camera straight on. |
VFaceInstructionNeedHeadShakeHorizontal | Initiate movement for liveness, a gental side to side head shake. |
VFaceInstructionNeedUprightPhone | Hold the device upright (based on accelerometer data). |
VFaceInstructionNeedUpwardFacingPhone | Hold the device at an angle (based on accelerometer data). |
VFaceInstructionProcessing | VFace is processing. Do not request any specific actions. |
Dismissing VFace
On operation completion VFaceFragment
receives a call to dismiss(VFaceFinishReason reason)
, where VFaceFinishReason
values are:
Value | Description |
---|---|
SUCCESS_ENROLL | Enrolement was successful. |
SUCCESS_AUTHENTICATE | Authentication was successful. |
FAIL_ENROLL | Enrolment failed. |
FAIL_AUTHENTICATE | Authentication failed. |
FAIL_LIVENESS | Liveness failed. |
CANCEL | The user requested to cancel. |
TIMEOUT | Operation timed out before it could be completed. |
ERROR | An error occurred. |
Display to the user any relevant information and call finish()
on the fragment, CustomVFaceFragment.this.getActivity().finish()
.
Note: this is intended for informative UI within the biometric capture process only, and not to make decisions on how to proceed. Proceed according to the result of the biometric operation (see [Handling an Operational Result](#handling a result)).