Complete Manual for Facial Detection and Filling in Android Applications

Estimated time: 45-60 minutes for complete configuration


What will you learn from this manual?

That manual will teach you how to implement and use JAAKVisage SDK for automated face detection and video recording in native Android applications. 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

Contents Index

SectionWhat will you do?Time
Step 1Configure Project and Units10 min
Step 2Basic implementation of SDK20 min
Step 3Configure permits and manifests10 min
Step 4Record and video management15 min
Step 5Test facial detection10 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 / Kotlin18Yes

1.1 Building (Project) configuration

buildscript {
    ext.kotlin_version = "1.9.22"
    ext.hilt_version = '2.46'
    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 {
    namespace 'com.jaak.visagesdk'
    compileSdk 35

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

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_18
        targetCompatibility JavaVersion.VERSION_18
    }

    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")

    // JAAK Visage SDK
    implementation("com.jaak.visagesdk:jaakvisage-sdk:1.0.0-beta")
}

Step 2: Basic implementation of SDK

Objective

Create JAAKVisage's basic implementation SDK with face detection and automatic recording.

2.1 Application Class

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

@HiltAndroidApp
class JaakVisageSDKApp : 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 com.jaak.visagesdk.databinding.ActivityMainBinding
import com.jaak.visagesdk.ui.adapter.VisageListener
import com.jaak.visagesdk.ui.view.VisageSDK
import com.jaak.visagesdk.utils.CameraFacing
import com.jaak.visagesdk.utils.Utils
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity(), VisageListener {

    private lateinit var binding: ActivityMainBinding
    private lateinit var visageSDK: VisageSDK

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

        configureVisageSDK()

        binding.btnStartDetection.setOnClickListener {
            startFaceDetection()
        }
    }

    private fun configureVisageSDK() {
        visageSDK = VisageSDK(this, this)

        // Recommended facial detection configuration
        visageSDK.setCameraFacing(CameraFacing.FRONT) // Recommended front camera

        // Development tools (optional)
        visageSDK.setDebugMode(true)   // Real-time Metrics: FPS | MEM | FACE | MSC
        visageSDK.setConfigPanel(true) // Visible configuration panel
    }

    private fun startFaceDetection() {
        // The SDK automatically manages camera permissions
        visageSDK.startVisage() // Begins facial detection and automatic recording
    }

    override fun onSuccessVisage(typeProcess: Int, uri: Uri?) {
        // typeProcess is always 2 (face detection video)
        uri?.let { videoUri ->
            val base64Video = Utils.videoCameraUriToBase64(videoUri) ?: ""

            Log.d("Visage", "Video capturado: $videoUri")
            Log.d("Visage", "Base64 length: ${base64Video.length}")

            if (base64Video.isNotEmpty()) {
                Toast.makeText(this, "Facial video successfully processed", Toast.LENGTH_LONG).show()
                // Processing video as required
                processVideoForKYC(base64Video, videoUri)
            } else {
                Toast.makeText(this, "Failed to process video", Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onErrorVisage(text: String) {
        Log.e("Visage", "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("cancelled", ignoreCase = true) -> "User's cancelled process"
            else -> "Error: $text"
        }

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

    private fun processVideoForKYC(base64Video: String, originalUri: Uri) {
        // Create payload for backend
        val kycData = mapOf(
            "facialVideo" to base64Video,
            "timestamp" to System.currentTimeMillis(),
            "source" to "visage_sdk_android",
            "videoUri" to originalUri.toString()
        )

        Log.d("Visage", "Processing video for KYC: ${kycData.keys}")
        // Send to your backend /API for biometric analysis
    }
}

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 Visage SDK Android"
        android:textSize="20sp"
        android:textStyle="bold"
        android:layout_marginBottom="32dp"
        app:layout_constraintBottom_toTopOf="@+id/btnStartDetection"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/btnStartDetection"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Initiate Facial Detection"
        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.visagesdk.JaakVisageSDKApp"
        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.visagesdk.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.visagesdk"
            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_movies" path="Movies/" />
    <external-path name="my_files" path="Android/data/com.jaak.visagesdk/files/Jaak Visage SDK/" />
</paths>

Track 4: Record and Videos Management

Objective

Implement complete management of video prints and advanced processing.

4.1 Advanced implementation with configuration

class AdvancedVisageActivity : AppCompatActivity(), VisageListener {

    private lateinit var binding: ActivityAdvancedBinding
    private lateinit var visageSDK: VisageSDK

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

        setupAdvancedConfiguration()
    }

    private fun setupAdvancedConfiguration() {
        visageSDK = VisageSDK(this, this)

        // Camera Setup
        setupCameraSpinner()

        // Configure Main Button
        binding.btnLaunchVisage.setOnClickListener {
            if (validateAndApplyConfiguration()) {
                visageSDK.startVisage()
            }
        }

        // Default values
        loadDefaultValues()
    }

    private fun setupCameraSpinner() {
        val cameraOptions = arrayOf("Border Chamber", "Rear Camera")
        val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, cameraOptions)
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        binding.spinnerCameraFacing.adapter = adapter
    }

    private fun loadDefaultValues() {
        binding.spinnerCameraFacing.setSelection(0) // Default border
        binding.cbShowConfigPanel.isChecked = false
        binding.cbShowDebugMode.isChecked = false
    }

    private fun validateAndApplyConfiguration(): Boolean {
        try {
            // Configure Camera
            val cameraFacing = if (binding.spinnerCameraFacing.selectedItemPosition == 0)
                CameraFacing.FRONT else CameraFacing.BACK

            // Apply to SDK
            visageSDK.setCameraFacing(cameraFacing)
            visageSDK.setConfigPanel(binding.cbShowConfigPanel.isChecked)
            visageSDK.setDebugMode(binding.cbShowDebugMode.isChecked)

            return true
        } catch (e: Exception) {
            Toast.makeText(this, "Configure error: ${e.message}", Toast.LENGTH_SHORT).show()
            return false
        }
    }

    override fun onSuccessVisage(typeProcess: Int, uri: Uri?) {
        try {
            Log.d("AdvancedVisage", "Procesamiento exitoso - Tipo: $typeProcess")

            uri?.let { videoUri ->
                // Optimized conversion to Base64 for camera videos
                val base64Video = Utils.videoCameraUriToBase64(videoUri)

                if (!base64Video.isNullOrEmpty()) {
                    Log.d("AdvancedVisage", "Video convertido: ${base64Video.length} caracteres")

                    // Processing video as required
                    processCompleteVideo(base64Video, videoUri)

                    Toast.makeText(this, "Facial video successfully processed", Toast.LENGTH_LONG).show()
                } else {
                    Log.e("AdvancedVisage", "Error: Can't convert video to Base64")
                    Toast.makeText(this, "Failed to process video", Toast.LENGTH_SHORT).show()
                }
            }
        } catch (e: Exception) {
            Log.e("AdvancedVisage", "Error processing result: ${.message}")
            Toast.makeText(this, "Unexpected error: ${e.message}", Toast.LENGTH_SHORT).show()
        }
    }

    private fun processCompleteVideo(base64Video: String, originalUri: Uri) {
        // Prepare complete data for KYC
        val kycData = mapOf(
            "facialVideo" to base64Video,
            "timestamp" to System.currentTimeMillis(),
            "source" to "visage_sdk_android_v1",
            "deviceInfo" to getDeviceInfo(),
            "videoUri" to originalUri.toString(),
            "sessionId" to generateSessionId()
        )

        // Send to backend for biometric analysis
        sendToKYCBackend(kycData)

        Log.d("AdvancedVisage", "Processed video for KYC completely")
    }

    private fun getDeviceInfo(): Map<String, String> {
        return mapOf(
            "model" to android.os.Build.MODEL,
            "manufacturer" to android.os.Build.MANUFACTURER,
            "sdkVersion" to android.os.Build.VERSION.SDK_INT.toString(),
            "appVersion" to packageManager.getPackageInfo(packageName, 0).versionName
        )
    }

    private fun generateSessionId(): String {
        return "visage_${System.currentTimeMillis()}_${(1000..9999).random()}"
    }

    override fun onErrorVisage(text: String) {
        Log.e("AdvancedVisage", "Error: $text")

        // Qualify errors for best management
        when {
            text.contains("permission", ignoreCase = true) -> handlePermissionError(text)
            text.contains("camera", ignoreCase = true) -> handleCameraError(text)
            text.contains("cancelled", ignoreCase = true) -> handleUserCancellation(text)
            else -> handleGenericError(text)
        }
    }

    private fun handlePermissionError(text: String) {
        Toast.makeText(this, "Camera permits required for face detection", Toast.LENGTH_LONG).show()
    }

    private fun handleCameraError(text: String) {
        Toast.makeText(this, "Camera error. Check that it isn't being used", Toast.LENGTH_LONG).show()
    }

    private fun handleUserCancellation(text: String) {
        Toast.makeText(this, "User's cancelled process", Toast.LENGTH_SHORT).show()
    }

    private fun handleGenericError(text: String) {
        Toast.makeText(this, "Error: $text", Toast.LENGTH_LONG).show()
    }
}

4.2 Usefulness of Video Analysis

class VideoAnalyzer {

    companion object {
        fun analyzeVideoQuality(base64Video: String): VideoQualityReport {
            return VideoQualityReport(
                sizeInBytes = base64Video.length * 3 / 4, // Size approach
                base64Length = base64Video.length,
                estimatedDurationSeconds = estimateDuration(base64Video),
                qualityScore = calculateQualityScore(base64Video)
            )
        }

        private fun estimateDuration(base64Video: String): Float {
            // Estimate (very rough)
            val sizeInMB = (base64Video.length * 3 / 4) / (1024 * 1024)
            return sizeInMB * 2 // Approximately 2 seconds per MB
        }

        private fun calculateQualityScore(base64Video: String): Float {
            // Simple score based on video size
            val sizeInMB = (base64Video.length * 3 / 4) / (1024 * 1024)
            return when {
                sizeInMB > 5 -> 0.9f
                sizeInMB > 2 -> 0.7f
                sizeInMB > 1 -> 0.5f
                else -> 0.3f
            }
        }
    }
}

data class VideoQualityReport(
    val sizeInBytes: Int,
    val base64Length: Int,
    val estimatedDurationSeconds: Float,
    val qualityScore: Float
)

Track 5: Test Facial Detection

Objective

Check that all functionalities of SDK They work properly.

checklist

ElementStateDescription
Camera permits☐App automatically requests and gets permission
Facial detection☐AI detects faces in real time
Automatic registration☐Reforms with proper face detection
Base64 conversion☐Videos become 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 Basic Detection

  1. Open Application and press "Start Facial Detection"
  2. Position face front to front camera
  3. To expect automatic detection (green corners)
  4. Check Auto Start Retirement
  5. Review login to confirm processing

Step C: Test Different Conditions

  1. Change light (light, artificial)
  2. Test distances (close, far from device)
  3. Check debug mode (FPS, memory, detection)
  4. Confirm videos are saved correctly

Problem solving

Common problems

ProblemResolution
"SDK does not initialise "Check Application class and dependencies
"Camera doesn't start."Check permissions and physical device
"No face detectors."Enhance light and use front camera
"Record Failed"Check storage space

Advanced Debug

private fun enableAdvancedDebugging() {
    visageSDK.setDebugMode(true)  // Muestra: FPS | MEM | FACE | CAM
    visageSDK.setConfigPanel(true) // Status Panel Sample

    Log.d("Visage", "= = = DIRECT INFORMATION = = =")
    Log.d("Visage", "Modelo: ${Build.MODEL}")
    Log.d("Visage", "Manufacturer: ${Build.MANUFACTURER}")
    Log.d("Visage", "SDK: ${Build.VERSION.SDK_INT}")
    Log.d("Visage", "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
VisageSDK (context, list)Building SDKVisageSDK(this, this)
startVisage ()Begins facial detection and recordingvisageSDK.startVisage()
SetCameraFacing (facing)Configure front / back cameravisageSDK.setCameraFacing(CameraFacing.FRONT)
SetDebugReal-time metric activityvisageSDK.setDebugMode(true)
SetConFigPanel (toolean)Configure Panel SamplevisageSDK.setConfigPanel(true)

Available

ConfigureDescriptionWhen to use
CameraFacing.FRONTFront cameraRecommended for faces
CameraFacing.BACKRearSpecial cases
SetDebugReal-time technologiesDuring development
SetConFigPanel (true)Visibility PanelTo debugging

Response structure

ResponseDescriptionWhen it's generated
onSuccess Visage (typeProcess, uri)Successful video engravetypeProcess = 2, uri video
onErrorsTest errorAny bug in detection / recording

Advanced Problem Resolution

Problem: "MML Kit model error"

// Check compatibility with ML Kit
private fun checkMLKitCompatibility() {
    try {
        // ML Kit starts automatically
        Log.d("Visage", "ML Kit available for face detection")
    } catch (e: Exception) {
        Log.e("Visage", "ML error Kit: ${e.message}")
        showError("Unsupported with face detection")
    }
}

Problem: "Videos aren't processed"

// Check video conversion
private fun debugVideoConversion(uri: Uri) {
    try {
        val base64Video = Utils.videoCameraUriToBase64(uri)

        if (base64Video.isNullOrEmpty()) {
            Log.e("Visage", "Error: Conversion to Base64 failed")
            Log.e("Visage", "URI: $uri")

            // Check if the file exists
            contentResolver.query(uri, null, null, null, null)?.use { cursor ->
                Log.d("Visage", "Archivo existe: ${cursor.count > 0}")
            }
        } else {
            Log.d("Visage", "Successful conversion: ${base64Vide.length} characters")
        }
    } catch (e: Exception) {
        Log.e("Visage", "Failed processing video: ${.message}")
    }
}

Do you need some help?

Support information

  • Problem description: What kind of detection are you trying to figure out?
  • Android Studio Logs: Screenshots of errors in Lochcat
  • Unit information: Model, Android version, RAM
  • Configure SDK: Parameters used
  • Test conditions: Lighting, distance, direction

Support Contact

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


You have successfully implemented JAAKVisage SDK Your app can now detect faces and record videos automatically using artificial intelligence, generating optimized files for biometric and KYC.