Developer Documentation

Alaqan Mini SDK
for Android

Integrate palm vein biometric authentication into your Android applications with our secure, easy-to-use SDK.

Version 2.0.0 March 2026 Android 7.0+

Introduction

The Alaqan Mini SDK enables Android applications to integrate palm vein biometric authentication using the Alaqan Mini hardware device. The SDK handles USB communication, real-time frame processing with hand detection and biometric operations for enrollment and identification.

High Security

Multi-spectral imaging with liveness detection for enterprise-grade security.

Fast Recognition

Sub-second identification performance for seamless user experience.

Easy Integration

Simple API with comprehensive callbacks and clear documentation.

Contactless

Hygienic palm vein recognition without physical contact.

Use Cases

  • Point of Sale (POS) authentication
  • Banking and financial services
  • Access control systems
  • Time and attendance tracking
  • Identity verification

Requirements

Hardware

ComponentRequirement
DeviceAlaqan Mini Palm Scanner
ConnectionUSB Type-C
Host DeviceAndroid device with OTG support

Software

ComponentRequirement
Android SDKMinimum API Level 24 (Android 7.0 Nougat)
Target SDKAPI Level 34 (Android 14) recommended
Architecturearm64-v8a, armeabi-v7a

Network

  • Active internet connection required for SDK activation and biometric operations
  • Minimum bandwidth: 1 Mbps recommended

Installation

1

Add the SDK to Your Project

Add the Alaqan Mini SDK AAR file to your project's libs directory:

app/
├── libs/
│   └── alaqan-mini-sdk.aar
├── src/
└── build.gradle
2

Configure Gradle

Add the following to your app-level build.gradle:

android {
    defaultConfig {
        minSdk 24
        ndk {
            abiFilters 'arm64-v8a', 'armeabi-v7a'
        }
    }
}

dependencies {
    implementation files('libs/alaqan-mini-sdk.aar')
    implementation 'androidx.appcompat:appcompat:1.6.1'
}
3

Configure USB Device Filter

Create res/xml/device_filter.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="17992" product-id="65280" />
</resources>
4

Update AndroidManifest.xml

Add the required permissions and USB configuration:

<manifest>
    <uses-feature android:name="android.hardware.usb.host" android:required="true" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application>
        <activity android:name=".MainActivity">
            <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>

Quick Start

The recommended integration uses the high-level API. The SDK handles camera setup, hand detection, capture and biometric matching automatically.

class MainActivity : AppCompatActivity(), AlaqanMiniListener {
    private val sdk = AlaqanMiniSDK()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sdk.setApiKey("YOUR_API_KEY")
        sdk.initialize(this)
    }

    override fun onResume() {
        super.onResume()
        sdk.setListener(this)
        if (sdk.isInitialized && !sdk.device.isConnected) {
            sdk.device.connect()
        }
    }

    override fun onPause() {
        sdk.setListener(null)
        super.onPause()
    }

    override fun onDestroy() {
        sdk.destroy()
        super.onDestroy()
    }

    override fun onInitialized() {
        sdk.device.connect()
    }

    override fun onDeviceConnected(device: Device) {
        // Device ready — start an identify operation
        sdk.startIdentify(object : IdentifyCallback {
            override fun onGuidance(guidance: Guidance) {
                statusText.text = when (guidance) {
                    Guidance.SHOW_PALM    -> "Show your palm"
                    Guidance.MOVE_CLOSER  -> "Move closer"
                    Guidance.MOVE_FURTHER -> "Move further"
                    Guidance.CENTER_PALM  -> "Center your palm"
                    Guidance.HOLD_STEADY  -> "Hold steady"
                    Guidance.CAPTURING    -> "Capturing..."
                    Guidance.PROCESSING   -> "Processing..."
                }
            }

            override fun onPreviewFrame(result: FrameResult) {
                result.bitmap?.let { imageView.setImageBitmap(it) }
            }

            override fun onResult(result: IdentifyResult) {
                if (result.isSuccess) {
                    Log.d("Alaqan", "Identified: ${result.name}")
                } else {
                    Log.e("Alaqan", "Failed: ${result.error}")
                }
            }
        })
    }

    override fun onDeviceDisconnected(device: Device) {
        Log.d("Alaqan", "Disconnected")
    }

    override fun onError(code: String) {
        Log.e("Alaqan", "Error: $code")
    }
}

SDK Architecture

Your Application
UI/UX, Business Logic
Alaqan Mini SDK
Device Management, Frame Processing, Biometric API
Alaqan Mini Device
IR + VL Cameras, Distance Sensor

Call startIdentify() or startEnroll() and the SDK handles camera setup, hand detection, capture and biometric matching. Use getPersons() and deletePerson() for user management.

Underlying layers:

  • Device layer — USB connection, camera control, distance sensing
  • Processing layer — Real-time hand detection and quality scoring
  • API layer — Enrollment, identification and person management

Lifecycle Management

The SDK follows Android lifecycle best practices. All callbacks are delivered on the main (UI) thread.

App StateRecommended SDK Action
Application.onCreate()setApiKey() + initialize()
Activity.onResume()setListener(this) + device.connect()
Activity.onPause()setListener(null)
Application terminationdestroy()
Tip

Create the SDK instance once (e.g. in your Application class) and share it across activities. Only one listener can be active at a time — set it in onResume() and clear it in onPause(). Use setGlobalListener() for persistent event handling across activity transitions.

High-Level API

Call startIdentify() or startEnroll() and the SDK handles camera setup, detection, capture and biometric matching. You receive guidance updates, preview frames and a final result through a callback.

Only one operation can run at a time. Call stopOperation() to cancel.

Operation Control

fun startIdentify(callback: IdentifyCallback)
fun startEnroll(externalId: String, name: String, metadata: String?, callback: EnrollCallback)
fun stopOperation()
fun setOperationTimeout(ms: Long)           // default 30000, 0 = no timeout
fun getOperationState(): OperationState      // IDLE, DETECTING, CAPTURING, PROCESSING
fun getOperationInfo(): OperationInfo?       // null when idle

OperationState

ValueDescription
IDLENo operation running
DETECTINGWaiting for a hand with sufficient quality and steadiness
CAPTURINGSteady hand found, capturing frames
PROCESSINGFrames captured, processing in progress

OperationInfo

FieldTypeDescription
typeOperationInfo.TypeIDENTIFY or ENROLL
stateOperationStateCurrent phase of the operation

Identify

Starts an identify operation. The SDK detects the user's palm, captures frames and returns a match result.

sdk.startIdentify(object : IdentifyCallback {
    override fun onPreviewFrame(result: FrameResult) {
        // Optional: display camera preview
        result.bitmap?.let { imageView.setImageBitmap(it) }
    }

    override fun onGuidance(guidance: Guidance) {
        // Update UI with positioning instructions
        statusText.text = guidanceToString(guidance)
    }

    override fun onResult(result: IdentifyResult) {
        if (result.isSuccess) {
            showMatch(result.externalId, result.name, result.metadata)
        } else {
            showError(result.error)
        }
    }
})

IdentifyResult

FieldTypeDescription
scanIdString?Scan identifier
externalIdString?Matched user's external ID
nameString?Matched user's name
metadataString?JSON metadata set during enrollment
errorString?An ErrorCodes constant or error message (null on success)
fun isSuccess(): Boolean  // true if error == null

Enroll

Starts an enroll operation. The SDK captures multiple frames and returns an enrollment result. The onProgress callback reports capture progress.

sdk.startEnroll("user-123", "John Doe", null, object : EnrollCallback {
    override fun onPreviewFrame(result: FrameResult) {
        result.bitmap?.let { imageView.setImageBitmap(it) }
    }

    override fun onGuidance(guidance: Guidance) {
        statusText.text = guidanceToString(guidance)
    }

    override fun onProgress(captured: Int, total: Int) {
        progressBar.progress = captured * 100 / total
    }

    override fun onResult(result: EnrollResult) {
        if (result.isSuccess) {
            showSuccess(result.scanId)
        } else {
            showError(result.error)
        }
    }
})

EnrollResult

FieldTypeDescription
scanIdString?Scan identifier
errorString?An ErrorCodes constant or error message (null on success)
fun isSuccess(): Boolean  // true if error == null

Guidance

The Guidance enum provides real-time positioning hints during identify and enroll operations. Map these to your own localized UI strings.

ValueDescription
SHOW_PALMNo hand detected — prompt the user to show their palm
MOVE_CLOSERHand is too far from the device
MOVE_FURTHERHand is too close to the device
CENTER_PALMHand is off-center in the frame
HOLD_STEADYHand detected but not stable enough
CAPTURINGA frame has been captured (enroll only, may fire multiple times)
PROCESSINGCapture complete, processing biometric data

API Reference

Initialization

setApiKey()

Sets the API key used for SDK activation. Must be called before initialize().

fun setApiKey(apiKey: String?)

initialize()

Initializes the SDK asynchronously — activates with the API key and loads models. Fires onInitialized() on success or onError() on failure.

fun initialize(context: Context)

isInitialized()

fun isInitialized(): Boolean

getNativeVersion()

Returns the native library version string. Available after initialize().

fun getNativeVersion(): String?

setLedGateEnabled()

Enables or disables the LED gate for inference. When enabled (default), inference is skipped on frames where the LED is off. Disable to run inference on all frames regardless of LED state.

fun setLedGateEnabled(enabled: Boolean)  // default: true

setGlobalListener()

Sets a persistent listener that receives events regardless of the activity-scoped listener set via setListener(). Useful for handling device events in a service or application-level component that survives activity transitions.

fun setGlobalListener(listener: AlaqanMiniListener?)

destroy()

Releases all SDK resources including native memory and USB connections. Call in onDestroy().

fun destroy()

Device

Access the device instance via sdk.getDevice(). The Device class manages USB connection, cameras and sensors.

Connection

fun connect()
fun disconnect()
fun isConnected(): Boolean

Device Information

fun getSerialNumber(): String?   // e.g., "GX4TYQWH3D"
fun getFirmwareVersion(): String? // e.g., "1.2.3"

Distance Sensor

fun getDistance(): Int                          // current distance in mm, -1 if unavailable
fun getSensingRange(): IntArray?                  // [min, max] in mm, or null
fun setSensingRange(min: Int, max: Int)          // set valid range (default 70–170)

LED Control

fun setLedMode(mode: Int)          // Device.LED_AUTO (default) or Device.LED_MANUAL
fun isLedModeSupported(): Boolean  // check hardware capability first
ConstantValueDescription
Device.LED_AUTO0LEDs triggered automatically by the distance sensor
Device.LED_MANUAL1LEDs off; distance measurement continues

Device Management

fun reboot()  // triggers disconnect, then automatic reconnect

Camera

The device has two cameras: Camera.Type.IR (infrared) and Camera.Type.VL (visible light). The active camera provides the preview frames; the inference camera is used for hand detection.

Camera Selection

fun setActiveCamera(type: Camera.Type)        // preview camera
fun setInferenceCamera(type: Camera.Type?)    // detection camera (null = same as active)
fun getActiveCamera(): Camera
fun getInferenceCamera(): Camera
fun getIrCamera(): Camera
fun getVlCamera(): Camera

Camera Properties

fun getType(): Camera.Type
fun isLedOn(): Boolean
fun getFrameCount(): Int

Person Management

Manage enrolled persons. These are blocking calls — run them on a background thread.

getPersons()

Retrieves a paginated list of enrolled persons.

fun getPersons(page: Int, perPage: Int): PersonsPage

PersonsPage

FieldTypeDescription
dataList<Person>List of persons on this page
totalIntTotal number of enrolled persons
pageIntCurrent page number
perPageIntItems per page
errorString?An ErrorCodes constant or error message (null on success)

Person

FieldTypeDescription
idIntPerson ID
externalIdString?Your external user ID
nameString?Display name
metadataString?JSON metadata set during enrollment
createdAtString?ISO 8601 creation timestamp

deletePerson()

Deletes an enrolled person by their ID.

fun deletePerson(personId: Int): DeleteResult

DeleteResult

FieldTypeDescription
errorString?An ErrorCodes constant or error message (null on success)
fun isSuccess(): Boolean  // true if error == null

Callbacks

AlaqanMiniListener

Receives SDK lifecycle and device connection events. All callbacks are delivered on the main thread. Set via setListener() (activity-scoped) or setGlobalListener() (persistent).

interface AlaqanMiniListener {
    fun onInitialized()
    fun onDeviceConnected(device: Device)
    fun onDeviceDisconnected(device: Device)
    fun onError(code: String)
}
CallbackWhen Fired
onInitialized()SDK activation and model loading completed successfully
onDeviceConnected(device)USB device connected and authenticated, streaming started
onDeviceDisconnected(device)USB device disconnected
onError(code)An error occurred — code is an ErrorCodes constant (see Error Codes)

IdentifyCallback

Receives events during a high-level identify operation started via startIdentify(). All methods are called on the main thread.

interface IdentifyCallback {
    fun onPreviewFrame(result: FrameResult)  // default no-op, override for preview
    fun onGuidance(guidance: Guidance)
    fun onResult(result: IdentifyResult)
}
CallbackWhen Fired
onPreviewFrame(result)Each processed camera frame (default no-op, override to display preview)
onGuidance(guidance)Positioning hint changed — update your UI accordingly
onResult(result)Exactly once when the operation completes (success, error, timeout, or cancel)

EnrollCallback

Receives events during a high-level enroll operation started via startEnroll(). All methods are called on the main thread.

interface EnrollCallback {
    fun onPreviewFrame(result: FrameResult)  // default no-op, override for preview
    fun onGuidance(guidance: Guidance)
    fun onProgress(captured: Int, total: Int)
    fun onResult(result: EnrollResult)
}
CallbackWhen Fired
onPreviewFrame(result)Each processed camera frame (default no-op, override to display preview)
onGuidance(guidance)Positioning hint changed — update your UI accordingly
onProgress(captured, total)After each successful frame capture — update a progress indicator
onResult(result)Exactly once when the operation completes (success, error, timeout, or cancel)

FrameCallback

Callback that receives every processed camera frame. Frame processing only runs while a callback is registered. Set to null when frames are not needed to save CPU and battery.

interface FrameCallback {
    fun onFrameProcessed(result: FrameResult)
}

Register via sdk.setFrameCallback(callback). Useful for displaying a live camera preview outside of identify/enroll operations.

Example

sdk.setFrameCallback { result ->
    result.bitmap?.let { imageView.setImageBitmap(it) }
}

// Stop when not needed
sdk.setFrameCallback(null)

Data Models

FrameResult

Delivered via FrameCallback.onFrameProcessed() or the high-level callback's onPreviewFrame() for each camera frame.

FieldTypeDescription
jpegSizeIntSize of the raw JPEG frame in bytes
widthIntFrame width in pixels
heightIntFrame height in pixels
bitmapBitmap?Decoded frame image for display (shared — do not recycle)
handsHandDetection[]?Detected hands in the frame
moreHandsBooleanTrue if additional hands were detected beyond the limit
inferenceMsFloatTotal inference time in milliseconds
ledOnBooleanLED state when frame was captured
steadyBooleanTrue if hand bounding box is stable across consecutive detections

HandDetection

FieldTypeDescription
confidenceFloatDetection confidence (0.0 - 1.0)
x, y, w, hFloatBounding box in pixel coordinates
qualityFloatQuality score (0.0 - 1.0), or -1 if not computed

Result Objects

All result objects follow the same pattern: check isSuccess() first. On failure, the error field contains either an ErrorCodes constant (4-digit string like "5002") or a human-readable error message.

Result ClassFieldsUsed By
IdentifyResultscanId, externalId, name, metadata, errorstartIdentify()
EnrollResultscanId, errorstartEnroll()
DeleteResulterrordeletePerson()
PersonsPagedata, total, page, perPage, errorgetPersons()

Error Codes

Error codes are 4-digit string constants defined in ErrorCodes. They are delivered via onError(code: String) on the listener and as the error field on result objects.

1xxx — Device / USB

CodeConstantDescription
1001ErrorCodes.USB_PERMISSION_DENIEDUSB permission denied by user
1002ErrorCodes.USB_NO_INTERFACENo USB interface found on device
1003ErrorCodes.USB_NO_ENDPOINTSMissing required USB endpoints
1004ErrorCodes.USB_OPEN_FAILEDFailed to open USB device
1005ErrorCodes.USB_CLAIM_FAILEDFailed to claim USB interface
1006ErrorCodes.USB_NO_SERIALDevice serial number unavailable
1007ErrorCodes.DEVICE_NOT_RESPONDINGDevice connected but not sending frames

2xxx — Authentication

CodeConstantDescription
2001ErrorCodes.AUTH_CHALLENGE_FAILEDChallenge request failed
2002ErrorCodes.AUTH_INVALID_REPLYInvalid challenge reply from device
2003ErrorCodes.AUTH_COMPUTE_FAILEDAuthentication computation failed
2004ErrorCodes.AUTH_VERIFY_FAILEDVerify command failed to send
2005ErrorCodes.AUTH_REJECTEDDevice rejected authentication

3xxx — Streaming

CodeConstantDescription
3001ErrorCodes.STREAM_START_FAILEDFailed to send start stream command
3002ErrorCodes.STREAM_REJECTEDDevice rejected stream start

4xxx — SDK

CodeConstantDescription
4001ErrorCodes.NATIVE_CREATE_FAILEDNative context creation failed
4002ErrorCodes.MODEL_LOAD_FAILEDModel loading failed

5xxx — Network

CodeConstantDescription
5001ErrorCodes.API_KEY_MISSINGAPI key not set
5002ErrorCodes.SERVER_NETWORK_ERRORNetwork error during request
5003ErrorCodes.SERVER_INVALID_RESPONSEInvalid or unexpected response
5004ErrorCodes.SERVER_REJECTEDRequest rejected
5005ErrorCodes.SERVER_TIMEOUTRequest timed out

6xxx — Operations

CodeConstantDescription
6001ErrorCodes.OP_ALREADY_RUNNINGAnother operation is already running
6002ErrorCodes.OP_CANCELLEDOperation cancelled via stopOperation()
6003ErrorCodes.OP_TIMEOUTOperation timed out
6004ErrorCodes.OP_DISCONNECTEDDevice disconnected during operation
6005ErrorCodes.OP_NOT_INITIALIZEDSDK not initialized when operation was started
6006ErrorCodes.OP_NOT_CONNECTEDDevice not connected when operation was started
6007ErrorCodes.OP_INVALID_PARAMSInvalid parameters (e.g., null externalId for enroll)

Result objects may also contain a human-readable error message in the error field when a request is rejected.

Best Practices

Tip

Always check isConnected() before starting enrollment or identification operations.

High-Level API (Recommended)

For most integrations, use startIdentify() / startEnroll(). The SDK manages camera, detection and biometric matching for you:

// Identify — simplest possible integration
sdk.startIdentify(object : IdentifyCallback {
    override fun onGuidance(guidance: Guidance) {
        updateStatusUI(guidance)
    }
    override fun onResult(result: IdentifyResult) {
        if (result.isSuccess) handleMatch(result)
        else handleError(result.error)
    }
})

// Cancel if the user navigates away
sdk.stopOperation()

Camera Configuration

For enrollment and identification, display the VL camera while running detection on the IR camera:

sdk.device.setActiveCamera(Camera.Type.VL)       // user sees visible light
sdk.device.setInferenceCamera(Camera.Type.IR)    // detection on infrared

Threading

  • getPersons() and deletePerson() are blocking calls — always run them on a background thread
  • All listener and callback methods are delivered on the main thread — safe to update UI directly

Error Handling

override fun onError(code: String) {
    when {
        code.startsWith("1") -> handleDeviceError(code)
        code.startsWith("2") -> handleAuthError(code)
        code.startsWith("5") -> handleNetworkError(code)
        code == ErrorCodes.DEVICE_NOT_RESPONDING -> {
            // Device stopped sending frames — will auto-disconnect
        }
        else -> Log.e("Alaqan", "Error: $code")
    }
}

Troubleshooting

Device Not Detected

Symptoms: onDeviceConnected() never called

  • Verify Alaqan Mini is properly connected via USB-C
  • Check USB OTG is enabled on the Android device
  • Verify device_filter.xml contains correct VID/PID values
  • Try a different USB cable

USB Permission Denied

Symptoms: onError() with code ErrorCodes.USB_PERMISSION_DENIED ("1001")

  • Ensure USB permission dialog is shown and accepted
  • Check that your Activity handles USB intents correctly
  • Clear app data and try again

SDK Initialization Fails

Symptoms: onError() with code ErrorCodes.API_KEY_MISSING ("5001") or ErrorCodes.SERVER_NETWORK_ERROR ("5002")

  • Verify API key is correct and not expired
  • Ensure active internet connection
  • Check logcat for detailed error messages

Low Detection Quality

Symptoms: quality stays below threshold

  • Ensure proper hand distance (check getDistance())
  • Guide the user to center their palm in the frame
  • Make sure the LED is on (camera.isLedOn())
  • Avoid direct sunlight on the scanner
  • Remove the protective film from the scanner glass

Device Not Responding

Symptoms: onError() with code ErrorCodes.DEVICE_NOT_RESPONDING ("1007")

  • The device stopped sending frames — SDK will auto-disconnect
  • Try device.reboot() to restart the device
  • Check USB cable connection

Sample Implementation

Contact Alaqan support for a complete sample project with full source code.

Support

Technical Support

support@alaqan.com

Business Inquiries

sales@alaqan.com

When Reporting Issues

Include: Error code, steps to reproduce, device serial number and firmware version, SDK version.