Alaqan Mini SDK
for Android
Integrate palm vein biometric authentication into your Android applications with our secure, easy-to-use SDK.
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
| Component | Requirement |
|---|---|
| Device | Alaqan Mini Palm Scanner |
| Connection | USB Type-C |
| Host Device | Android device with OTG support |
Software
| Component | Requirement |
|---|---|
| Android SDK | Minimum API Level 24 (Android 7.0 Nougat) |
| Target SDK | API Level 34 (Android 14) recommended |
| Architecture | arm64-v8a, armeabi-v7a |
Network
- Active internet connection required for SDK activation and biometric operations
- Minimum bandwidth: 1 Mbps recommended
Installation
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
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'
}
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>
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
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 State | Recommended SDK Action |
|---|---|
Application.onCreate() | setApiKey() + initialize() |
Activity.onResume() | setListener(this) + device.connect() |
Activity.onPause() | setListener(null) |
Application termination | destroy() |
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
| Value | Description |
|---|---|
IDLE | No operation running |
DETECTING | Waiting for a hand with sufficient quality and steadiness |
CAPTURING | Steady hand found, capturing frames |
PROCESSING | Frames captured, processing in progress |
OperationInfo
| Field | Type | Description |
|---|---|---|
type | OperationInfo.Type | IDENTIFY or ENROLL |
state | OperationState | Current 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
| Field | Type | Description |
|---|---|---|
scanId | String? | Scan identifier |
externalId | String? | Matched user's external ID |
name | String? | Matched user's name |
metadata | String? | JSON metadata set during enrollment |
error | String? | 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
| Field | Type | Description |
|---|---|---|
scanId | String? | Scan identifier |
error | String? | 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.
| Value | Description |
|---|---|
SHOW_PALM | No hand detected — prompt the user to show their palm |
MOVE_CLOSER | Hand is too far from the device |
MOVE_FURTHER | Hand is too close to the device |
CENTER_PALM | Hand is off-center in the frame |
HOLD_STEADY | Hand detected but not stable enough |
CAPTURING | A frame has been captured (enroll only, may fire multiple times) |
PROCESSING | Capture 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
| Constant | Value | Description |
|---|---|---|
Device.LED_AUTO | 0 | LEDs triggered automatically by the distance sensor |
Device.LED_MANUAL | 1 | LEDs 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
| Field | Type | Description |
|---|---|---|
data | List<Person> | List of persons on this page |
total | Int | Total number of enrolled persons |
page | Int | Current page number |
perPage | Int | Items per page |
error | String? | An ErrorCodes constant or error message (null on success) |
Person
| Field | Type | Description |
|---|---|---|
id | Int | Person ID |
externalId | String? | Your external user ID |
name | String? | Display name |
metadata | String? | JSON metadata set during enrollment |
createdAt | String? | ISO 8601 creation timestamp |
deletePerson()
Deletes an enrolled person by their ID.
fun deletePerson(personId: Int): DeleteResult
DeleteResult
| Field | Type | Description |
|---|---|---|
error | String? | 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)
}
| Callback | When 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)
}
| Callback | When 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)
}
| Callback | When 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.
| Field | Type | Description |
|---|---|---|
jpegSize | Int | Size of the raw JPEG frame in bytes |
width | Int | Frame width in pixels |
height | Int | Frame height in pixels |
bitmap | Bitmap? | Decoded frame image for display (shared — do not recycle) |
hands | HandDetection[]? | Detected hands in the frame |
moreHands | Boolean | True if additional hands were detected beyond the limit |
inferenceMs | Float | Total inference time in milliseconds |
ledOn | Boolean | LED state when frame was captured |
steady | Boolean | True if hand bounding box is stable across consecutive detections |
HandDetection
| Field | Type | Description |
|---|---|---|
confidence | Float | Detection confidence (0.0 - 1.0) |
x, y, w, h | Float | Bounding box in pixel coordinates |
quality | Float | Quality 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 Class | Fields | Used By |
|---|---|---|
IdentifyResult | scanId, externalId, name, metadata, error | startIdentify() |
EnrollResult | scanId, error | startEnroll() |
DeleteResult | error | deletePerson() |
PersonsPage | data, total, page, perPage, error | getPersons() |
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
| Code | Constant | Description |
|---|---|---|
| 1001 | ErrorCodes.USB_PERMISSION_DENIED | USB permission denied by user |
| 1002 | ErrorCodes.USB_NO_INTERFACE | No USB interface found on device |
| 1003 | ErrorCodes.USB_NO_ENDPOINTS | Missing required USB endpoints |
| 1004 | ErrorCodes.USB_OPEN_FAILED | Failed to open USB device |
| 1005 | ErrorCodes.USB_CLAIM_FAILED | Failed to claim USB interface |
| 1006 | ErrorCodes.USB_NO_SERIAL | Device serial number unavailable |
| 1007 | ErrorCodes.DEVICE_NOT_RESPONDING | Device connected but not sending frames |
2xxx — Authentication
| Code | Constant | Description |
|---|---|---|
| 2001 | ErrorCodes.AUTH_CHALLENGE_FAILED | Challenge request failed |
| 2002 | ErrorCodes.AUTH_INVALID_REPLY | Invalid challenge reply from device |
| 2003 | ErrorCodes.AUTH_COMPUTE_FAILED | Authentication computation failed |
| 2004 | ErrorCodes.AUTH_VERIFY_FAILED | Verify command failed to send |
| 2005 | ErrorCodes.AUTH_REJECTED | Device rejected authentication |
3xxx — Streaming
| Code | Constant | Description |
|---|---|---|
| 3001 | ErrorCodes.STREAM_START_FAILED | Failed to send start stream command |
| 3002 | ErrorCodes.STREAM_REJECTED | Device rejected stream start |
4xxx — SDK
| Code | Constant | Description |
|---|---|---|
| 4001 | ErrorCodes.NATIVE_CREATE_FAILED | Native context creation failed |
| 4002 | ErrorCodes.MODEL_LOAD_FAILED | Model loading failed |
5xxx — Network
| Code | Constant | Description |
|---|---|---|
| 5001 | ErrorCodes.API_KEY_MISSING | API key not set |
| 5002 | ErrorCodes.SERVER_NETWORK_ERROR | Network error during request |
| 5003 | ErrorCodes.SERVER_INVALID_RESPONSE | Invalid or unexpected response |
| 5004 | ErrorCodes.SERVER_REJECTED | Request rejected |
| 5005 | ErrorCodes.SERVER_TIMEOUT | Request timed out |
6xxx — Operations
| Code | Constant | Description |
|---|---|---|
| 6001 | ErrorCodes.OP_ALREADY_RUNNING | Another operation is already running |
| 6002 | ErrorCodes.OP_CANCELLED | Operation cancelled via stopOperation() |
| 6003 | ErrorCodes.OP_TIMEOUT | Operation timed out |
| 6004 | ErrorCodes.OP_DISCONNECTED | Device disconnected during operation |
| 6005 | ErrorCodes.OP_NOT_INITIALIZED | SDK not initialized when operation was started |
| 6006 | ErrorCodes.OP_NOT_CONNECTED | Device not connected when operation was started |
| 6007 | ErrorCodes.OP_INVALID_PARAMS | Invalid 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
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()anddeletePerson()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.xmlcontains 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
Include: Error code, steps to reproduce, device serial number and firmware version, SDK version.
