---
title: "Android On Device Age Estimation"
url: "https://developer.incode.com/sdk-reference/android-on-device-age-estimation/"
section: "sdk-reference"
group: "Android SDK"
version: "v1.1"
status: "live"
---
# Android On Device Age Estimation
On-Device Age Estimation runs the face-analysis pipeline - face detection, positioning, liveness, and age estimation - entirely on the user's device during Face Capture (the `SelfieScan` module). On this path, no selfie image, video, or biometric frames are uploaded to Incode. Only structured analysis results (for example: liveness, face quality, and the estimated age) are submitted to the Incode backend. On-device processing requires E2EE: the SDK enforces it for the session and encrypts the submitted metadata client-side.

Because the biometric capture never leaves the device, the feature also skips the normal selfie image upload, video/frame recording upload, and manual capture. Age decisioning is performed on the backend from the submitted results.

> 📘 Availability
>
> On-Device Age Estimation is available on Android Welcome SDK `5.47.0` and later, in `STANDARD` mode only. It requires using UX V2, the `SelfieScan` module in `SelfieScan.Mode.ENROLL`, and is not supported for Face Login.

## Prerequisites

This guide assumes the Welcome SDK is already installed and initializing in your app. If you are starting from scratch, complete [Android SDK Setup](/sdk-reference/android-installation) (GitHub Packages, dependencies, permissions) and the [SDK Integration guide](/sdk-reference/android-sdk) first, then return here.

Before you enable On-Device Age Estimation, make sure the following are in place:

- **Android Welcome SDK `5.47.0` or later** in `SdkMode.STANDARD`.
- **The two on-device model dependencies** added to your app's module `build.gradle` / `build.gradle.kts`. These are optional for the Welcome SDK overall, but **required** for On-Device Age Estimation:

```groovy build.gradle (Module)
dependencies {
    // Required for On-Device Age Estimation
    implementation 'com.incode.sdk:model-liveness-detection'
    implementation 'com.incode.sdk:model-age-estimation'
}
```
```kotlin build.gradle.kts (Module)
dependencies {
    // Required for On-Device Age Estimation
    implementation("com.incode.sdk:model-liveness-detection")
    implementation("com.incode.sdk:model-age-estimation")
}
```

  Versions are managed by the Incode BOM. See the [BOM Version Mapping](/release-notes/bom-version-mapping) for the versions bundled with each SDK release. If either dependency is missing while the feature is enabled, `SelfieScan.Builder.build()` throws `MissingModelLivenessDetectionDependencyException` or `MissingModelAgeEstimationDependencyException`.

- **E2EE enabled for your organization and a valid E2EE URL.** End-to-end encryption is required for on-device processing. If E2EE is not enabled on the session, the flow ends with `OnDeviceProcessingNotCompatibleException` (reason `MISSING_E2EE`). See the [E2EE Integration Guide](/sdk-reference/android-e2ee) for the full setup.
- **A Flow configured in the Incode Dashboard with an accessible Flow ID** (used as the `configurationId` on the session) if you use the server-driven `startFlow()` path.
- **No conflicting capture routing.** Frame streaming and screen recording must be disabled on this path, because both would send frames off-device. See [Error handling](#error-handling) for the exact conditions.
- **The V2 (Compose) selfie UI.** On-device processing runs only on the V2 selfie UI; a flow routed through the legacy V1 UI raises `OnDeviceProcessingNotCompatibleException` (reason `V1_NOT_SUPPORTED`). See [Enable the V2 UI](#enable-the-v2-ui) for how to turn it on per start method.

> 📘 Minimum SDK
>
> The `model-age-estimation` module targets the SDK's mandatory `minSdk 23` and ships the same ABIs as the rest of the SDK, so it adds no new device or hardware gate for this feature. See [Android SDK Setup](/sdk-reference/android-installation) for the supported ABIs and base packaging.

## Configure On-Device Processing

For server-driven flows, log in to the Dashboard to enable **On-Device Processing** on the Face Capture module configuration in your Flow or Workflow.

### Server-driven Flows and Workflows (recommended)

1. In the left menu, click **Flow Builder** > **Flows** OR **Workflows**.
2. Open your Flow or Workflow.
3. Open the configuration panel for the Face Capture module.
4. Enable **On-Device Processing**. This ensures that liveness and age estimation run entirely on the user's device. No biometric images leave the SDK.
5. If using Workflows, click **Save configurations**. Flow configurations save automatically.
6. Once the configuration panel closes, if using Workflows, click **Save & Publish**. If using Flows, click **Save Changes**.

The SDK reads this setting from the flow automatically. There is no code flag to set.

### Client-configured Flows (alternative)

If you build your own `FlowConfig` in code and start the flow with `startOnboarding()`, set the flag explicitly with `SelfieScan.Builder().setOnDeviceFaceResultsSubmissionEnabled(true)` (see step 3 below). The flag defaults to `false`.

## Enable the V2 UI

On-device age estimation runs only on the V2 (Compose) selfie UI. A Flow on the legacy V1 UI raises `OnDeviceProcessingNotCompatibleException` (reason `V1_NOT_SUPPORTED`). Turn V2 on the way you would for any V2 integration. For the full mechanism and the ECSM caveat, see [Enabling the `V2` UI](/sdk-reference/android-customization#enable-v2-ui) in the SDK Customization guide.

### Server-driven Flows and Workflows (recommended)

1. In the left menu, click **Flow Builder** > **Flows** OR **Workflows**.
2. Open your Flow or Workflow configured for on-device processing.
3. Click the **Settings** tab.
4. Toggle **Enable new user experience** on.
5. For Flows, click **Save Changes**. For Workflows, click **Save & Publish**.

### Client-configured Flows (alternative)

Enable V2 explicitly at initialization with `IncodeWelcome.Builder.setClientExperimentId("experimentV2")` (see step 1 below).

## Integration steps

### 1. Initialize the SDK

Initialize the Welcome SDK, supplying your custom E2EE server URL as the fourth constructor argument, then obtain the singleton instance.

```kotlin
IncodeWelcome.Builder(this, WELCOME_API_URL, WELCOME_API_KEY, E2EE_URL)
    // Opts client-configured flows (startOnboarding) into UX V2 - see "Enabling the V2 UI".
    .setClientExperimentId("experimentV2")
    .build()

val incodeWelcome = IncodeWelcome.getInstance()
```
```java
new IncodeWelcome.Builder(this, WELCOME_API_URL, WELCOME_API_KEY, E2EE_URL)
    // Opts client-configured flows (startOnboarding) into UX V2 - see "Enabling the V2 UI".
    .setClientExperimentId("experimentV2")
    .build();

IncodeWelcome incodeWelcome = IncodeWelcome.getInstance();
```

### 2. Create the session configuration

Create a `SessionConfig` that references your Dashboard Flow ID and enables E2EE. The `configurationId` is identical to the Dashboard flow ID (use "Copy flow ID" in the Dashboard).

```kotlin
val sessionConfig: SessionConfig = SessionConfig.Builder()
    .setConfigurationId(FLOW_ID)
    .setE2eEncryptionEnabled(true)
    .build()
```
```java
SessionConfig sessionConfig = new SessionConfig.Builder()
    .setConfigurationId(FLOW_ID)
    .setE2eEncryptionEnabled(true)
    .build();
```

Because `setE2eEncryptionEnabled()` is a likely target for fraudsters, protect this code against reverse engineering. See the [E2EE Integration Guide](/sdk-reference/android-e2ee) for the full hardening guidance.

### 3. Start the flow

Unlike iOS, Android does not use a presenting view controller. You pass an Android `Context` and the SDK launches its own activities.

**Server-driven flow (recommended).** With on-device processing enabled in the Dashboard, no code flag is needed. Start the flow with `startFlow()`:

```kotlin
IncodeWelcome.getInstance().startFlow(
    activityContext,
    sessionConfig,
    onboardingListener)
```
```java
IncodeWelcome.getInstance().startFlow(
    activityContext,
    sessionConfig,
    onboardingListener);
```

**Server-driven workflow.** Workflows are also server-driven. Enable "On-Device Processing" on the Face Capture step of your Workflow configuration in the Dashboard, set the session's `configurationId` to the Workflow ID, and start it with `startWorkflow()`. No code flag is needed:

```kotlin
IncodeWelcome.getInstance().startWorkflow(
    activityContext,
    sessionConfig,
    onboardingListener)
```
```java
IncodeWelcome.getInstance().startWorkflow(
    activityContext,
    sessionConfig,
    onboardingListener);
```

**Client-configured flow (alternative).** If you build the `FlowConfig` in code, enable the feature on the `SelfieScan` module and start the flow with `startOnboarding()`:

```kotlin
val flowConfig: FlowConfig = FlowConfig.Builder()
    // ... other modules
    .addSelfieScan(
        SelfieScan.Builder()
            .setOnDeviceFaceResultsSubmissionEnabled(true)
            .build())
    .build()

IncodeWelcome.getInstance().startOnboarding(
    activityContext,
    sessionConfig,
    flowConfig,
    onboardingListener)
```
```java
FlowConfig flowConfig = new FlowConfig.Builder()
    // ... other modules
    .addSelfieScan(
        new SelfieScan.Builder()
            .setOnDeviceFaceResultsSubmissionEnabled(true)
            .build())
    .build();

IncodeWelcome.getInstance().startOnboarding(
    activityContext,
    sessionConfig,
    flowConfig,
    onboardingListener);
```

The SDK validates on-device compatibility at flow start on every path. If any requirement is not met, the flow ends via `OnboardingListener.onError(...)` (see [Error handling](#error-handling)).

### 4. Handle the result

The `SelfieScanResult` is delivered to your `OnboardingListener` through `onSelfieScanCompleted(selfieScanResult)`. Errors are delivered asynchronously to `onError(error)`.

```kotlin
val onboardingListener = object : IncodeWelcome.OnboardingListener {
    // ... other flow callbacks

    override fun onSelfieScanCompleted(selfieScanResult: SelfieScanResult) {
        // The selfie image and video were never uploaded to Incode on this path.
        // Continue your flow using the structured result.
    }

    override fun onError(error: Throwable) {
        if (error is OnDeviceProcessingNotCompatibleException) {
            // A requirement for on-device processing was not met.
        }
    }
}
```
```java
IncodeWelcome.OnboardingListener onboardingListener = new IncodeWelcome.OnboardingListener() {
    // ... other flow callbacks

    @Override
    public void onSelfieScanCompleted(SelfieScanResult selfieScanResult) {
        // The selfie image and video were never uploaded to Incode on this path.
        // Continue your flow using the structured result.
    }

    @Override
    public void onError(Throwable error) {
        if (error instanceof OnDeviceProcessingNotCompatibleException) {
            // A requirement for on-device processing was not met.
        }
    }
};
```

> 📘 Result on the on-device path
>
> Because no server-side face evaluation runs on this path, `SelfieScanResult.isSpoofAttempt` stays `null`. The estimated age is computed on-device and submitted to Incode as part of the structured results; it is not returned in `SelfieScanResult`.
>
> The `SelfieScanResult` still contains the locally captured selfie image (`selfieBase64` / `fullFrameSelfieBase64`, and the local `croppedSelfieImgPath` / `fullFrameSelfieImgPath`). The on-device guarantee is that these image bytes are never uploaded to Incode - not that the local result is image-less. Handle them on-device according to your own privacy requirements.

## Compatible configuration matrix

On-device age estimation only works in a narrow, privacy-preserving configuration. Unusual Flow / Workflow / `FlowConfig` combinations that are valid for a normal Face Capture will abort the session when on-device processing is enabled. Use this matrix when configuring any on-device age-estimation integration.

| Setting | Required value | Where to set it | If wrong |
|:---|:---|:---|:---|
| SDK mode | `SdkMode.STANDARD` | App init | Feature unavailable |
| Welcome SDK version | `5.47.0`+ | App dependencies / BOM | Feature unavailable |
| Model artifacts | `model-liveness-detection` **and** `model-age-estimation` present | App `build.gradle` | `MissingModel*DependencyException` at `SelfieScan.Builder.build()` |
| E2EE | Enabled on the session + valid E2EE URL at SDK init | `SessionConfig.setE2eEncryptionEnabled(true)` + 4-arg `IncodeWelcome.Builder` | `OnDeviceProcessingNotCompatibleException` (`MISSING_E2EE`) |
| On-Device Processing | **On** for the Face Capture module | Dashboard Face Capture config, **or** `SelfieScan.Builder().setOnDeviceFaceResultsSubmissionEnabled(true)` for client-built flows | On-device path not entered |
| UX V2 | **On** ("Enable new user experience") | Dashboard Flow/Workflow **Settings**, **or** `setClientExperimentId("experimentV2")` for client-built flows | `OnDeviceProcessingNotCompatibleException` (`V1_NOT_SUPPORTED`) |
| Selfie mode | `SelfieScan.Mode.ENROLL` | Face Capture / `SelfieScan` config | Face Login / auth modes are not supported |
| Frame streaming / "Enable face recording" | **Off** | Dashboard Face Capture, **or** do not call `setStreamFramesEnabled(true)` | `OnDeviceProcessingNotCompatibleException` (`FRAME_STREAMING`) |
| Screen recording | **Off** | `FlowConfig.Builder.configureScreenRecording(false, ...)` | `OnDeviceProcessingNotCompatibleException` (`SCREEN_RECORDING`) |
| Manual capture | Disabled by the SDK on this path | N/A (not configurable when on-device is on) | Manual-capture UI / timeout fallbacks do not apply; keep auto-capture healthy |
| Start path | `startFlow` / `startWorkflow` (recommended) or `startOnboarding` with the flag above | App code | Same compatibility checks run on every path |

> 📘 Expected differences vs a normal Face Capture
>
> On this path the SDK does **not** upload the selfie image or video, does **not** expose manual capture, and does **not** run server-side face evaluation (`SelfieScanResult.isSpoofAttempt` stays `null`). Server-side checks that depend on a stored selfie (for example Face Recognition / Face Match scores) may return N/A even if those modules appear later in the same Flow or Workflow. Configure age decisioning from the on-device structured results your backend receives, not from selfie-dependent scores.

## Error handling

Two distinct failures can occur when the feature is misconfigured:

- **`MissingModelLivenessDetectionDependencyException` / `MissingModelAgeEstimationDependencyException`** - thrown synchronously from `SelfieScan.Builder.build()` when the feature is enabled but the required optional model dependency is absent. Fix by adding the dependencies from [Prerequisites](#prerequisites).
- **`OnDeviceProcessingNotCompatibleException`** - delivered asynchronously to `OnboardingListener.onError(...)` at flow start when the configuration is incompatible. It carries one of the following reasons:

| Reason            | Meaning                                                                  | Fix                                                                                    |
|:------------------|:-------------------------------------------------------------------------|:---------------------------------------------------------------------------------------|
| `MISSING_E2EE`    | E2EE is not enabled on the session.                                      | Call `SessionConfig.Builder.setE2eEncryptionEnabled(true)`.                             |
| `FRAME_STREAMING` | Frame streaming is enabled on the same `SelfieScan`.                     | Disable frame streaming (do not call `setStreamFramesEnabled(true)` / face recording). |
| `SCREEN_RECORDING`| Screen recording is configured for the flow.                             | Call `FlowConfig.Builder.configureScreenRecording(false, ...)`.                        |
| `V1_NOT_SUPPORTED`| The flow routes through the legacy V1 selfie UI (for example Face Login).| Use the V2 selfie UI in enrollment mode.                                               |

## Related guides

- [Android SDK Setup](/sdk-reference/android-installation) - base install, dependencies, and permissions this guide builds on.
- [SDK Integration guide](/sdk-reference/android-sdk) - the core onboarding integration.
- [E2EE Integration Guide](/sdk-reference/android-e2ee) - required setup for on-device processing.
- [SDK Customization guide](/sdk-reference/android-customization#enable-v2-ui) - how to enable and customize the `V2` UI.
- [BOM Version Mapping](/release-notes/bom-version-mapping) - versions of the `model-age-estimation` and `model-liveness-detection` dependencies.