Skip to content

DEA SDK for Android

Detect whether an app session comes from a real person. The SDK observes touch, stylus, mouse, scroll and device-motion input across the screens of your Android app and scores the session on the device.

On this page: Prerequisites · Install · Initialize the SDK · Ask for consent · Use the classification · Verify your setup · Before you release · Next steps

Prerequisites

  • An appId and appKey for this app — see Accounts and keys
  • minSdk 21 (Android 5.0) or later. Attest needs a device on API 23 (Android 6.0) or later.
  • Kotlin or Java. The API is written in Kotlin; the samples in this guide are Kotlin.

The SDK is compiled with Kotlin 2.2.20 and Android Gradle Plugin 9.4.0, against compileSdk 35, with Java 17 source and target compatibility. It ships native code for arm64-v8a, armeabi-v7a, x86 and x86_64, so it runs on devices and on emulators.

Install

Not available yet. The current release, 0.19.0, is not published for Android. DEA's Maven repository, https://sdk.dep-protocol.com/maven, serves ai.dea:dea-sdk 0.2.2 only, an earlier release whose API differs from this guide, and Maven Central has no DEA package. There is no dependency line that installs the SDK this guide describes.

The ai.dea:dea-sdk package depends on its companion library ai.dea:dea-base at the same version, and on Google's Play Integrity library. Do not declare dea-base yourself at a different version.

Platform setup

No setup is needed for capture. The SDK adds the INTERNET permission to your app's manifest through manifest merging; it asks for no runtime permission. Motion sensors on Android need no permission.

If your app restricts ABIs with ndk { abiFilters ... } or ABI splits, keep the ABIs your users' devices need — the SDK cannot score on an ABI that was filtered out.

Attest has additional Play Integrity setup.

Initialize the SDK

Initialize once, in your Application.onCreate(), so that capture covers the whole session from the first screen.

import ai.dea.sdk.DeaConfig
import ai.dea.sdk.DeaSdk
import android.app.Application

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        DeaSdk.init(
            this,
            DeaConfig(
                appId = "YOUR_APP_ID",
                appKey = "YOUR_APP_KEY",
            ),
        )
    }
}

Register the class in AndroidManifest.xml if you do not have one already:

<application
    android:name=".MyApp"
    ... >

appId and appKey are the pair DEA issued for this Android app; see Accounts and keys. The SDK sends appKey in the X-App-Key header when it registers the device key with DEA (Attest). Without it, attest() answers UNSUPPORTED without contacting DEA. Reading the classification does not use appKey: scoring runs on the device.

  • Call init on the main thread.
  • init is safe to call more than once. A repeat call returns the running instance and keeps its options.
  • init never throws. If the scoring engine cannot start, the SDK keeps capturing without a score, the classification stays INSUFFICIENT, and the SDK reports the problem on coreFault.
  • Pass your Application. The SDK keeps the Context you pass for as long as it runs, so an Activity passed here stays in memory after it is destroyed.
  • Capture is global: the window of every activity, the motion sensors, and app lifecycle. There is no base Activity to extend, no view to wrap, and nothing to register per screen. Touches inside a dialog or a popup window, which Android gives a window of its own, are not captured.
  • At start, the SDK checks DEA's model host for a newer scoring model, in the background. See Model options and Privacy and network.
  • Nothing is recorded until the user consents. See the next step.

Note: If you initialize from an Activity instead, for example after your own onboarding, the window already on screen is captured only when that activity is the Context you pass, and the SDK then keeps that activity in memory. Activities started after init are captured whichever Context you pass.

Anywhere else in your code, get the running instance with DeaSdk.instance:

val dea = DeaSdk.instance // null before init

By default the SDK records nothing until the user agrees. This step is required: an app that never asks collects no data and never gets a classification.

The SDK has a ready-made consent dialog. Show it on your first screen:

import ai.dea.sdk.DeaConsentDialog
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class HomeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_home)

        DeaConsentDialog.show(this) { granted ->
            // The user's answer, or the answer already on file.
        }
    }
}
  • The answer is stored on the device. Once the user has answered, show displays nothing and calls back at once with the stored answer, so you can call it on every launch.
  • Closing the dialog with the back button counts as no for this callback, but is not stored: the user is asked again next time.
  • Call it on the main thread, with an activity that is showing.

To ask in your own UI instead, show your prompt when consent.isAnswered is false and record the answer with setConsent:

import ai.dea.sdk.DeaSdk
import android.app.AlertDialog
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class HomeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_home)

        val dea = DeaSdk.instance ?: return
        if (!dea.consent.isAnswered) {
            AlertDialog.Builder(this)
                .setTitle("Help us keep bots out")
                .setMessage("We measure how you interact with the app to tell people apart from bots.")
                .setCancelable(false)
                .setPositiveButton("Allow") { _, _ -> dea.setConsent(true) }
                .setNegativeButton("Not now") { _, _ -> dea.setConsent(false) }
                .show()
        }
    }
}

See User Consent for the dialog's wording, withdrawing, asking again, and shared devices.

Use the classification

Read the classification at the moment you make a decision. The read is synchronous and fast enough for the main thread.

import ai.dea.sdk.ClassificationResult
import ai.dea.sdk.DeaSdk

fun onSignUp(form: SignUpForm) {
    when (DeaSdk.instance?.getClassification()?.result) {
        ClassificationResult.HUMAN -> submitSignUp(form)
        ClassificationResult.BOT -> rejectSignUp()
        else -> showChallenge() // UNDETERMINED or INSUFFICIENT: a captcha, a delay, a manual review
    }
}

getClassification() answers HUMAN, BOT, UNDETERMINED or INSUFFICIENT. What each one allows is your decision. Right after launch it is INSUFFICIENT: the session is not sufficient until it has as many actions — taps, drags, scrolls — as the model asks for, 8 with the built-in model. See Evidence.

Note: Before you block users on getClassification(), read Going to production for the state of the model in the current release.

Verify your setup

  1. Turn on debug logging:
DeaSdk.init(this, DeaConfig(appId = "YOUR_APP_ID", appKey = "YOUR_APP_KEY", debug = true))
  1. Run a debug build and open Logcat in Android Studio, filtered on [dea]. Accept your consent prompt and touch the screen. You should see a line per captured event:
I/System.out: [dea] PointerObject session=3f0c…
I/System.out: [dea] MotionObject session=3f0c…

If you see nothing after touching the screen, see Troubleshooting.

  1. Check the SDK from code:
import android.util.Log
import ai.dea.sdk.DeaSdk

val dea = DeaSdk.instance!!
Log.d("MyApp", "ready: ${dea.ready}")              // true
Log.d("MyApp", "fault: ${dea.coreFault}")          // null
Log.d("MyApp", "consent: ${dea.consent.decision}") // GRANTED
Log.d("MyApp", "model: ${dea.modelInfo}")          // its version, and where it came from

val info = dea.sessionInfo
Log.d("MyApp", "session: ${info.session}")         // a UUID
Log.d("MyApp", "sdk: ${info.sdk.version}")

Turn debug off before you ship.

Before you release

  • [ ] debug is false
  • [ ] appKey is passed to DeaSdk.init — see Options
  • [ ] Your app asks for consent — see User Consent
  • [ ] The classification is read at the decision point, with a fallback for UNDETERMINED and INSUFFICIENT — see Human Scoring
  • [ ] Capture is paused on password and payment screens — see Sensitive Screens
  • [ ] reset() is called on logout — see Sessions
  • [ ] attest() and renewAttestation() are called off the main thread — see Attest
  • [ ] The app is set up for Play Integrity — see Attest
  • [ ] You have read Going to production

Next steps