Complete Manual for Facial Detection 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 SDK Face Detector JAAK for real-time facial recognition 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 device with API 26 + (Android 8.0 +)
  • Access to Maren Repository JAAK
  • Basic knowledge of Kotlin / Java
  • Configure camera and storage permits

Contents Index

SectionWhat will you do?Time
Step 1Configure Project and Units10 min
Step 2Basic implementation of SDK20 min
Step 3Configure Permissions and FileProvider10 min
Step 4Management of responses and base6415 min
Step 5Test functionalities10 min

Step 1: Configure Project and Units

Objective

Configure the development environment and add the necessary SDK.

Technical requirements

RequirementVersionMandatory?
Android StudioIguanaYes
Fat8.4+Yes
minSdk26Yes
targetSdkVersion33Yes
Kotlin1.9.22+Yes

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 Settings.gradle configuration

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven {
            url 'https://us-maven.pkg.dev/jaak-platform/jaak-android'
        }
    }
}

1.3 Build.gradle configuration (Module app)

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

android {
    compileSdk 35

    defaultConfig {
        minSdk 26
        targetSdk 33
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_18
        targetCompatibility JavaVersion.VERSION_18
    }

    buildFeatures {
        viewBinding = true
    }
}

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
    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.dagger:hilt-android:$hilt_version")
    kapt("com.google.dagger:hilt-android-compiler:$hilt_version")

    // Face Detector SDK
    implementation("com.jaak.facedetector:jaakfacedetector-sdk:2.0.5")
}

Step 2: Basic implementation of SDK

Objective

To create the basic implementation of the Face Detector SDK Your application.

2.1 Create Application Class

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

@HiltAndroidApp
class TestApp : Application()

2.2 Main activity

import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.jaak.facedetector.FaceDetectorSDK
import com.jaak.facedetector.FaceDetectorListener
import com.example.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity(), FaceDetectorListener {

    private lateinit var binding: ActivityMainBinding
    private lateinit var faceDetectorSDK: FaceDetectorSDK

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

        setupFaceDetector()
        setupButtons()
    }

    private fun setupFaceDetector() {
        faceDetectorSDK = FaceDetectorSDK(this, this)

        // Basic configuration
        faceDetectorSDK.setEnableCamera(true)
        faceDetectorSDK.setEnableDisk(true)
        faceDetectorSDK.setEnableCameraPhoto(true)
        faceDetectorSDK.setEnableCameraVideo(true)
        faceDetectorSDK.setEnableDiskPhoto(true)
        faceDetectorSDK.setEnableDiskVideo(true)
        faceDetectorSDK.setFacesOrDocuments(true)
        faceDetectorSDK.setImageFormat("image/*")
        faceDetectorSDK.setVideoFormat("video/*")
        faceDetectorSDK.setImageSize(3)
        faceDetectorSDK.setVideoSize(10)
    }

    private fun setupButtons() {
        binding.btnFullMenu.setOnClickListener {
            faceDetectorSDK.startFaceDetector(0) // Full Menu
        }

        binding.btnCameraOnly.setOnClickListener {
            faceDetectorSDK.startFaceDetector(2) // Just camera
        }

        binding.btnStorageOnly.setOnClickListener {
            faceDetectorSDK.startFaceDetector(4) // Storage only
        }
    }

    override fun onSuccessFaceDetector(typeProcess: Int, uri: Uri?) {
        // Manejar resultado exitoso
        handleSuccess(typeProcess, uri)
    }

    override fun onErrorFaceDetector(text: String) {
        // Handling mistakes
        handleError(text)
    }

    private fun handleSuccess(typeProcess: Int, uri: Uri?) {
        val message = when (typeProcess) {
            1 -> "Photo captured from camera"
            2 -> "Video captured from camera"
            3 -> "Photo select from storage"
            4 -> "Selected video from storage"
            else -> "Completion"
        }

        binding.tvStatus.text = message
        // Procure file if necessary
        uri?.let { processFile(it, typeProcess) }
    }

    private fun handleError(text: String) {
        binding.tvStatus.text = "Error: $text"
    }

    private fun processFile(uri: Uri, type: Int) {
        // Here you can process the captured file
        // For example, convert to base64 or send it to a server
    }
}

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"
    android:padding="20dp">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="JAAK Face Detector"
        android:textSize="24sp"
        android:textStyle="bold"
        android:textAlignment="center"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="20dp" />

    <TextView
        android:id="@+id/tvStatus"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Press a button to start"
        android:textAlignment="center"
        android:padding="15dp"
        android:background="@drawable/rounded_background"
        app:layout_constraintTop_toBottomOf="@id/tvTitle"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="30dp" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center"
        app:layout_constraintTop_toBottomOf="@id/tvStatus"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginTop="40dp">

        <Button
            android:id="@+id/btnFullMenu"
            android:layout_width="280dp"
            android:layout_height="60dp"
            android:text="Complete Menu"
            android:textSize="16sp"
            android:layout_marginBottom="15dp" />

        <Button
            android:id="@+id/btnCameraOnly"
            android:layout_width="280dp"
            android:layout_height="60dp"
            android:text="Camera only"
            android:textSize="16sp"
            android:layout_marginBottom="15dp" />

        <Button
            android:id="@+id/btnStorageOnly"
            android:layout_width="280dp"
            android:layout_height="60dp"
            android:text="Storage only"
            android:textSize="16sp" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Passage 3: Configure Permises and FileProvider

Objective

Configure camera and storage permits and FileProvider for file management.

3.1 AndroidManifes.xml

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

    <!-- Permisos necesarios -->
    <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=".TestApp"
        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=".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>

        <!-- FileProvider para manejo seguro de archivos -->
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

    </application>
</manifest>

3.2 Create file _ paths.xml

Create File res/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/${applicationId}/files/" />
    <cache-path name="my_cache" path="." />
</paths>

3.3 Performance of Permises at Runtime

import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import android.Manifest
import android.content.pm.PackageManager

class MainActivity : AppCompatActivity(), FaceDetectorListener {

    companion object {
        private const val CAMERA_PERMISSION_CODE = 100
        private const val STORAGE_PERMISSION_CODE = 101
    }

    private fun checkAndRequestPermissions() {
        val permissionsNeeded = mutableListOf<String>()

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
            permissionsNeeded.add(Manifest.permission.CAMERA)
        }

        if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.P) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
                permissionsNeeded.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            }
        }

        if (permissionsNeeded.isNotEmpty()) {
            ActivityCompat.requestPermissions(
                this,
                permissionsNeeded.toTypedArray(),
                CAMERA_PERMISSION_CODE
            )
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        when (requestCode) {
            CAMERA_PERMISSION_CODE -> {
                if (grantResults.isNotEmpty() &&
                    grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
                    binding.tvStatus.text = "Permisos concedidos"
                } else {
                    binding.tvStatus.text = "Permisos denegados"
                }
            }
        }
    }
}

PASO 4: Answers and Base64 Conversion Management

Objective

Implement complete management of responses from SDK and conversion to base64.

4.1 Utils class for Base64 conversion

import android.content.ContentResolver
import android.net.Uri
import android.util.Base64
import java.io.ByteArrayOutputStream
import java.io.FileInputStream
import java.io.InputStream

object Utils {

    fun imageCameraUriToBase64(uri: Uri): String? {
        return try {
            val inputStream = FileInputStream(uri.path)
            val bytes = inputStream.readBytes()
            inputStream.close()
            Base64.encodeToString(bytes, Base64.DEFAULT)
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }

    fun imageFileUriToBase64(contentResolver: ContentResolver, uri: Uri): String? {
        return try {
            val inputStream = contentResolver.openInputStream(uri)
            val bytes = inputStream?.readBytes()
            inputStream?.close()
            bytes?.let { Base64.encodeToString(it, Base64.DEFAULT) }
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }

    fun videoCameraUriToBase64(uri: Uri): String? {
        return try {
            val inputStream = FileInputStream(uri.path)
            val bytes = inputStream.readBytes()
            inputStream.close()
            Base64.encodeToString(bytes, Base64.DEFAULT)
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }

    fun videoFileUriToBase64(contentResolver: ContentResolver, uri: Uri): String? {
        return try {
            val inputStream = contentResolver.openInputStream(uri)
            val bytes = inputStream?.readBytes()
            inputStream?.close()
            bytes?.let { Base64.encodeToString(it, Base64.DEFAULT) }
        } catch (e: Exception) {
            e.printStackTrace()
            null
        }
    }
}

4.2 Complete Management of Responses

override fun onSuccessFaceDetector(typeProcess: Int, uri: Uri?) {
    var base64Process = ""
    var message = ""

    uri?.let { fileUri ->
        when (typeProcess) {
            1 -> {
                // Photo from camera
                base64Process = Utils.imageCameraUriToBase64(fileUri) ?: ""
                message = "Photo captured from camera"
            }
            2 -> {
                // Video from camera
                base64Process = Utils.videoCameraUriToBase64(fileUri) ?: ""
                message = "Video captured from camera"
            }
            3 -> {
                // Photo from storage
                base64Process = Utils.imageFileUriToBase64(contentResolver, fileUri) ?: ""
                message = "Photo select from storage"
            }
            4 -> {
                // Video from storage
                base64Process = Utils.videoFileUriToBase64(contentResolver, fileUri) ?: ""
                message = "Selected video from storage"
            }
        }

        binding.tvStatus.text = message

        // Procure the base64 file
        if (base64Process.isNotEmpty()) {
            processBase64File(base64Process, typeProcess)
            showFileInfo(fileUri, base64Process.length)
        }
    }
}

override fun onErrorFaceDetector(text: String) {
    binding.tvStatus.text = "Error: $text"
    // Log for debugging
    android.util.Log.e("FaceDetector", "Error: $text")
}

private fun processBase64File(base64: String, type: Int) {
    // Here you can process the base64 file
    // Example: send them to a server, save them locally, etc.

    android.util.Log.d("FaceDetector", "Reprocessing Base64 file type: $type")
    android.util.Log.d("FaceDetector", "Basal size 64: ${base64.length} characters")

    // Example of sending to server
    // sendToServer(base64, type)
}

private fun showFileInfo(uri: Uri, base64Length: Int) {
    val fileInfo = "Archivo procesado:\n" +
            "URI: ${uri.path}\n" +
            "Basal size 64: ${base64Length} characters\ n" +
            "approx.: ${base64Length * 3 / 4 / 1024} KB"

    // Show information on an additional TextView or log
    android.util.Log.i("FaceDetector", fileInfo)
}

Step 5: Test Functionality

Objective

Check that all functionalities of SDK They work properly.

checklist

ElementStateDescription
Camera permits☐Application requests and gets permits
Photo capture☐It works from camera
Video capture☐It works from camera
File selection☐It works from storage
Basal conversion64☐Files are turned right

Test process

Step A: Test Permissions

  1. Install Application as a physical device
  2. Open Application first time
  3. Check request automatic permits
  4. Grant permits and storage

Step B: Test Capture from Camera

  1. Press "Just Camera"
  2. Take photo using the camera interface
  3. Check message success on screen
  4. Review login to confirm base64 conversion

Step C: Test Selection from Storage

  1. Press "Just Storage"
  2. Select File of images or videos
  3. Check processing file
  4. Confirm conversion a base64

Debugging and Troublehooting

Useful Loks for Debug

private fun enableDebugLogs() {
    // Add to onCreate () for debugging
    android.util.Log.d("FaceDetector", "=== INICIANDO DEBUG ===")
    android.util.Log.d("FaceDetector", "SDK version: 2.0.5")
    android.util.Log.d("FaceDetector", "Camera permits: ${checkCameraPermission ()}")
    android.util.Log.d("FaceDetector", "Permisos almacenamiento: ${checkStoragePermission()}")
}

private fun checkCameraPermission(): Boolean {
    return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ==
           PackageManager.PERMISSION_GRANTED
}

private fun checkStoragePermission(): Boolean {
    return if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.P) {
        ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
        PackageManager.PERMISSION_GRANTED
    } else {
        true // No required on Android 10 +
    }
}

Common problems

ProblemResolution
"Permission error"Check AndroidManifes.xml and Runtime permissions
"FileProvider error"Review file settings _ paths.xml
"Unfound file"Check routes and FileProvider authorities
"SDK doesn't answer "Check Hill dependencies and configuration

Full reference

Methods SDK

MethodDescriptionExample
SetEnableCamera (Boolean)Capture from camerafaceDetectorSDK.setEnableCamera(true)
SetEnableDesk (Boolean)Enabling selection from storagefaceDetectorSDK.setEnableDisk(true)
SetImageSize (Int)Maximum image size in MBfaceDetectorSDK.setImageSize(3)
SetVideoSize (Int)Maximum video size in MBfaceDetectorSDK.setVideoSize(10)
startFaceDetector (Int)Start the processfaceDetectorSDK.startFaceDetector(0)

Types of Process

TypeDescriptionWhen used
0Full MenuShow all options
2Just cameraFilling capture from camera
4Storage onlyForging file selection

Response Codes

CodeDescription
1Photo captured from camera
2Video captured from camera
3Photo select from storage
4Selected video from storage

Problem solving

Problem: "compilation error"

// Incorrecto
implementation("com.jaak.facedetector:jaakfacedetector-sdk:2.0.5")

// Correct - Check Repository Access
maven {
    url 'https://us-maven.pkg.dev/jaak-platform/jaak-android'
}

Problem: "Rejected permits"

// solution: Check Settings if permits are blocked
private fun openAppSettings() {
    val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
    val uri = Uri.fromParts("package", packageName, null)
    intent.data = uri
    startActivity(intent)
}

Problem: "FileProvider authority conflicts"

<!-- Usar applicationId para evitar conflictos -->
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">

Do you need some help?

Support information

  • Problem description: What are you trying to do with what's going on?
  • Android Studio Logs: Screenshots of errors and warnings
  • Building Setup Unit versions
  • Test device: Model, Android version, API level
  • Replay steps: The exact sequence caused by the error

Support Contact

Email: support Always include: Logs, configuration, version of SDK


You have successfully implemented the SDK Face Detector JAAK your Android app. Your app can now capture and process face images / videos for KYC and biometric monitoring.