A complete manual for document capture in Android applications

Estimated time: 60-75 minutes for complete configuration


What will you learn from this manual?

This manual will teach you how to implement and use JAAKStamps SDK for automated capture of identity documents in native Android applications with artificial intelligence. No advanced technical skills required - Just follow the steps.


Before Start - Checks list

Be sure to have these elements ready:

  • Android Studio Iguana installed
  • Android 6.0 + device (API 23+)
  • Gratle 8.4 configured
  • Basic knowledge about Kotlin / Android
  • Access to a functional camera
  • Identification document (INE, IFE, Passport...) for testing

Contents Index

SectionWhat will you do?Time
Step 1Configure Project and Units15 min
Step 2Basic implementation of SDK25 min
Step 3Configure permits and manifests10 min
Step 4Management of responses and images15 min
Step 5Test document capture10 min

Step 1: Configure Project and Units

Objective

Configure Android development environment and add necessary dependencies to SDK.

Technical requirements

RequirementVersionMandatory?
Android StudioIguanaYes
minSdk23 (Android 6.0 +)Yes
targetSdkVersion33Yes
compileSandk35Yes
Fat8.4Yes
Java / Kotlin17Yes

1.1 Building (Project) configuration

buildscript {
    ext.kotlin_version = "1.9.22"
    ext.hilt_version = '2.48'
    repositories {
        google()
        mavenCentral()
        maven {
            url 'https://us-maven.pkg.dev/jaak-platform/jaak-android'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:8.3.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

1.2 Building Setup (Module: app)

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

android {
    compileSdk 35

    defaultConfig {
        applicationId "Here."
        minSdk 23
        targetSdk 33
        versionCode 1
        versionName "1.0"
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }

    kotlinOptions {
        jvmTarget = '17'
    }

    buildFeatures {
        viewBinding = true
    }
}

dependencies {
    // Android Core
    implementation("androidx.core:core-ktx:1.15.0")
    implementation("androidx.appcompat:appcompat:1.7.0")
    implementation("androidx.constraintlayout:constraintlayout:2.2.0")
    implementation 'com.google.android.material:material:1.12.0'

    // Dependency Injection
    implementation("com.google.dagger:hilt-android:$hilt_version")
    kapt("com.google.dagger:hilt-android-compiler:$hilt_version")

    // CameraX
    implementation 'androidx.camera:camera-core:1.3.4'
    implementation 'androidx.camera:camera-camera2:1.3.4'
    implementation 'androidx.camera:camera-lifecycle:1.3.4'
    implementation 'androidx.camera:camera-view:1.3.4'

    // TensFlow Lite for AI
    implementation 'org.tensorflow:tensorflow-lite:2.14.0'
    implementation 'org.tensorflow:tensorflow-lite-support:0.4.4'

    // ONNX Runtime for classification
    implementation 'com.microsoft.onnxruntime:onnxruntime-android:1.16.3'

    // JAAK Stamps SDK
    implementation("com.jaak.stampssdk:jaakstamps-sdk:1.0.0-beta")
}

Step 2: Basic implementation of SDK

Objective

Create JAAKStamps core implementation SDK with an automated capture.

2.1 Application Class

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class JaakStampsSDKApp : Application()

2.2 Main activity

import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.jaak.stampssdk.databinding.ActivityMainBinding
import com.jaak.stampssdk.sdk.StampsSDK
import com.jaak.stampssdk.ui.adapter.StampsListener
import com.jaak.stampssdk.utils.Utils
import com.jaak.stampssdk.utils.CameraFacing
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity(), StampsListener {

    private lateinit var binding: ActivityMainBinding
    private lateinit var stampsSDK: StampsSDK

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        installSplashScreen()
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        configureStampsSDK()

        binding.btnStartScanner.setOnClickListener {
            startDocumentDetection()
        }
    }

    private fun configureStampsSDK() {
        stampsSDK = StampsSDK(this, this)

        // Recommended configuration
        stampsSDK.setAlignmentTolerance(15)        // Linkage accuracy (0-100)
        stampsSDK.setMaskSize(80)                  // Detection area (50-100)
        stampsSDK.setCropMargin(100)               // Filling mark (0-100)
        stampsSDK.setCaptureDelay(1500)            // Delay before capture (ms)
        stampsSDK.setCameraFacing(CameraFacing.BACK)  // Rear
        stampsSDK.setAutoStampsClassification(true)   // Automatic classification with A

        // Development tools (optional)
        stampsSDK.setShowConfigPanel(false)
        stampsSDK.setShowDebugMode(false)
    }

    private fun startDocumentDetection() {
        // The SDK automatically manages camera permissions
        stampsSDK.startStamps(1) // Mode 1: Automatic detection with A
    }

    override fun onSuccessStamps(
        typeProcess: Int,
        frontOriginalUri: Uri?,
        frontCropUri: Uri?,
        backOriginalUri: Uri?,
        backCropUri: Uri?
    ) {
        when (typeProcess) {
            1 -> handleStampsResults(frontOriginalUri, frontCropUri, backOriginalUri, backCropUri)
        }
    }

    private fun handleStampsResults(
        frontOriginal: Uri?,
        frontCrop: Uri?,
        backOriginal: Uri?,
        backCrop: Uri?
    ) {
        // Convert URIs to Base64 if necessary
        val frontOriginalBase64 = frontOriginal?.let {
            Utils.uriToBase64(contentResolver, it) ?: ""
        } ?: ""

        val frontCropBase64 = frontCrop?.let {
            Utils.uriToBase64(contentResolver, it) ?: ""
        } ?: ""

        Log.d("StampsResult", "Front Original: $frontOriginal")
        Log.d("StampsResult", "Front Crop: $frontCrop")
        Log.d("StampsResult", "Back Original: $backOriginal")
        Log.d("StampsResult", "Back Crop: $backCrop")

        Toast.makeText(this, "Documents successfully captured", Toast.LENGTH_SHORT).show()
    }

    override fun onErrorStamps(text: String) {
        Log.e("StampsError", text)
        Toast.makeText(this, "Error: $text", Toast.LENGTH_SHORT).show()
    }
}

2.3 Layout XML (activity _ main.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="JAAK Stamps SDK Android"
        android:textSize="20sp"
        android:textStyle="bold"
        android:layout_marginBottom="32dp"
        app:layout_constraintBottom_toTopOf="@+id/btnStartScanner"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/btnStartScanner"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Scan document"
        android:textSize="16sp"
        android:padding="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Step 3: Configure Permises and Manifesto

Objective

Configure the permissions and the Android manifesto correctly.

3.1 AndroidManifes.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        tools:replace="android:maxSdkVersion"
        android:maxSdkVersion="28" />

    <application
        android:name="com.jaak.stampssdk.JaakStampsSDKApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
        tools:replace="android:theme,android:name">

        <activity
            android:name="com.jaak.stampssdk.MainActivity"
            android:exported="true"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.jaak.stampssdk"
            android:exported="false"
            android:grantUriPermissions="true"
            tools:replace="android:authorities">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

    </application>
</manifest>

3.2 file _ paths.xml

Create re / xml / file _ paths.xml:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="Pictures/" />
    <external-path name="my_files" path="Android/data/com.jaak.stampssdk/files/Jaak Stamps SDK/" />
</paths>

Track 4: Reactions and Images Management

Objective

Implement complete management of captured images and processing.

4.1 Full Image Processing

private fun handleStampsResults(
    frontOriginal: Uri?,
    frontCrop: Uri?,
    backOriginal: Uri?,
    backCrop: Uri?
) {
    val documentImages = DocumentImages()

    // Full front processing
    frontOriginal?.let {
        documentImages.frontOriginalBase64 = Utils.uriToBase64(contentResolver, it) ?: ""
        documentImages.frontOriginalUri = it
    }

    // To process cut face
    frontCrop?.let {
        documentImages.frontCropBase64 = Utils.uriToBase64(contentResolver, it) ?: ""
        documentImages.frontCropUri = it
    }

    // Reg complete (if available)
    backOriginal?.let {
        documentImages.backOriginalBase64 = Utils.uriToBase64(contentResolver, it) ?: ""
        documentImages.backOriginalUri = it
    }

    // Procure cut back (if available)
    backCrop?.let {
        documentImages.backCropBase64 = Utils.uriToBase64(contentResolver, it) ?: ""
        documentImages.backCropUri = it
    }

    // Determine document type
    val documentType = if (backOriginal != null || backCrop != null) "double_sided" else "single_sided"

    // To process complete document
    processCompleteDocument(documentImages, documentType)

    Log.d("StampsResult", "Processed document: $documentType")
    Toast.makeText(this, "Document successfully captured", Toast.LENGTH_SHORT).show()
}

data class DocumentImages(
    var frontOriginalUri: Uri? = null,
    var frontCropUri: Uri? = null,
    var backOriginalUri: Uri? = null,
    var backCropUri: Uri? = null,
    var frontOriginalBase64: String? = null,
    var frontCropBase64: String? = null,
    var backOriginalBase64: String? = null,
    var backCropBase64: String? = null
)

private fun processCompleteDocument(images: DocumentImages, type: String) {
    // Create payload for backend
    val payload = mapOf(
        "documentType" to type,
        "timestamp" to System.currentTimeMillis(),
        "images" to mapOf(
            "frontOriginal" to (images.frontOriginalBase64 ?: ""),
            "frontCrop" to (images.frontCropBase64 ?: ""),
            "backOriginal" to (images.backOriginalBase64 ?: ""),
            "backCrop" to (images.backCropBase64 ?: "")
        ),
        "metadata" to mapOf(
            "platform" to "android",
            "sdkVersion" to "1.0.0-beta"
        )
    )

    // Send to backend or process locally
    Log.d("DocumentPayload", "Retirement ready for processing: ${payload.keys}")
}

4.2 Errors Management

override fun onErrorStamps(text: String) {
    Log.e("StampsError", "Reaping error: $text")

    val errorMessage = when {
        text.contains("permission", ignoreCase = true) -> "Required camera permits"
        text.contains("camera", ignoreCase = true) -> "Camera Error - Check that it isn't in use"
        text.contains("model", ignoreCase = true) -> "Error Loading A models - Reboot App"
        text.contains("detection", ignoreCase = true) -> "Undetected document - Enhancement of light"
        else -> "Error: $text"
    }

    Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show()
}

Step 5: Test document capture

Objective

Check that all functionalities of SDK They work properly.

checklist

ElementStateDescription
Camera permits☐App automatically requests and gets permission
Automatic detection☐AI detects document automatically
Imaging☐Original and face / back crop
Basal conversion64☐All images are turned right
Errors management☐Mistakes are handled correctly

Test process

Step A: Install and Configure

  1. Unit at Android Studio
  2. Install onto physical device (non-emulator)
  3. Check permits are requested automatically

Step B: Test Simple Capture

  1. Open Application and press "Scan Document"
  2. Position document within the framework
  3. To expect automatic detection (Green frame)
  4. Check automatic capture from the front
  5. Review login to confirm processing

Step C: Test Double face document

  1. Use INE or similar document with two faces
  2. Capture front automatically
  3. Following instructions to turn document
  4. Capture Reverse automatically
  5. Check 4 images at login

Problem solving

Common problems

ProblemResolution
"SDK Non-compatible "Check minSdk 23 + and dependencies
"Camera doesn't start."Check permissions and physical device
"Non-Loading Model"Reboot app and check memory
"Capture doesn't work."Using physical device, non-emulator

Advanced Debug

private fun enableDebugMode() {
    stampsSDK.setShowDebugMode(true)
    stampsSDK.setShowConfigPanel(true)

    Log.d("Debug", "= = = DIRECT INFORMATION = = =")
    Log.d("Debug", "Modelo: ${Build.MODEL}")
    Log.d("Debug", "SDK: ${Build.VERSION.SDK_INT}")
    Log.d("Debug", "RAM disponible: ${getAvailableMemory()}")
}

private fun getAvailableMemory(): String {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    val memoryInfo = ActivityManager.MemoryInfo()
    activityManager.getMemoryInfo(memoryInfo)
    return "${memoryInfo.availMem / (1024 * 1024)} MB"
}

Full reference

Methods SDK

MethodDescriptionExample
StampsSDK (context, list)Building SDKStampsSDK(this, this)
startStamps (model)Start capture (Mode 1: AI)stampsSDK.startStamps(1)
SetAlignmentTolerance (int)Linkage tolerance (0-100)stampsSDK.setAlignmentTolerance(15)
setMaskSize (int)Detection area (50-100)stampsSDK.setMaskSize(80)
SetCropMargin (int)Filling mark (0-100)stampsSDK.setCropMargin(100)
setCaptureDelay (int)Delay before capture (ms)stampsSDK.setCaptureDelay(1500)

Available

ConfigureDescriptionWhen to use
SetAutoStampsClassification (true)Automatic classification with ASmart type detection
SetCameraFacing (BACK)Camera selectionRear camera for best quality
SetShowDebugReal-time technologiesDuring development

Response structure

ImageDescriptionWhen it's generated
frontFull face imageAlways
frontCropUriFront cut by AIWhen detecting document
backOriginalUriFull back imageDouble face only
backCropUriReverse cut by AIWhen you get back

Advanced Problem Resolution

Problem: "A model error"

// Check compatibility before using
private fun checkAICompatibility() {
    val hasRequiredMemory = Runtime.getRuntime().maxMemory() > 512 * 1024 * 1024 // 512MB
    val hasRequiredAPI = Build.VERSION.SDK_INT >= 23

    if (!hasRequiredMemory || !hasRequiredAPI) {
        showError("Uncompatible with AI models")
        return
    }

    // Configure for low-resource devices
    if (Runtime.getRuntime().maxMemory() < 1024 * 1024 * 1024) { // < 1GB
        stampsSDK.setMaskSize(70) // Reduce processing area
        stampsSDK.setCaptureDelay(2000) // More time to process
    }
}

Problem: "Documents aren't detectable"

// Configure for better detection
private fun optimizeForDetection() {
    stampsSDK.setAlignmentTolerance(20) // More tolerant
    stampsSDK.setMaskSize(85) // bigger area
    stampsSDK.setCaptureDelay(2000) // More time to stabilise
    stampsSDK.setShowDebugMode(true) // See real-time metrics
}

Do you need some help?

Support information

  • Problem description: What document are you trying to capture vs. what's going on?
  • Android Studio Logs: Screenshots of errors in Lochcat
  • Unit information: Model, Android version, RAM
  • Free Configure: Unit versions
  • Type of document: INE, passport, license, etc.

Support Contact

Email: support Always include: Android logs, built-up configuration, version SDK


You have successfully implemented JAAKStamps SDK Your app can now capture document automatically using artificial intelligence, generating optimization images for processes KYC.