> ## Documentation Index
> Fetch the complete documentation index at: https://usefoil.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Android SDK

> Add the Foil Android SDK to your Kotlin app from Maven Central, configure it in Application.onCreate(), and hand sealed sessions to your backend to verify.

The Android SDK mirrors the browser SDK: configure it on startup, call `getSession()` when the user performs a sensitive action, and POST the sealed token to your backend for verification.

```kotlin theme={"dark"}
FoilClient.configure(
    context,
    FoilConfiguration(publishableKey = "pk_live_..."),
)

// At action time (signup, login, checkout)
val handoff = FoilClient.getSession()
```

<Note>
  The Android SDK is distributed through Maven Central as protected binary AAR
  artifacts. The public metadata repository is
  `https://github.com/abxy-labs/foil-android`.
</Note>

## Requirements

* Android 6.0 Marshmallow (API 23) and up for Android SDK 1.0.0+
* Kotlin 1.9+
* Android Gradle Plugin 8.2+
* Java 17 toolchain
* A Foil publishable key, starting with `pk_live_` or `pk_test_`

## Install

Foil publishes two Android artifacts:

| Artifact                       | Required | Description                                                                        |
| ------------------------------ | -------- | ---------------------------------------------------------------------------------- |
| `com.usefoil:foil-android`     | Yes      | Core Foil Android SDK.                                                             |
| `com.usefoil:foil-android-gms` | No       | Optional Google ecosystem helpers for apps that already ship Google Play Services. |

```kotlin theme={"dark"}
// settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

// app/build.gradle.kts
dependencies {
    implementation("com.usefoil:foil-android:1.0.0")
}
```

If your app already uses Google Play Services and wants the optional Google
continuity helpers, add the helper artifact too:

```kotlin theme={"dark"}
dependencies {
    implementation("com.usefoil:foil-android-gms:1.0.0")
}
```

The only required manifest permission is `android.permission.INTERNET`. The SDK declares no permissions of its own.

## Configure on startup

Call `configure()` from `Application.onCreate()` so signal collection is running by the time your first screen mounts.

```kotlin theme={"dark"}
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        FoilClient.configure(
            this,
            FoilConfiguration(publishableKey = "pk_live_your_publishable_key"),
        )
    }
}
```

### `FoilConfiguration`

```kotlin theme={"dark"}
data class FoilConfiguration(
    val publishableKey: String,
    val enableBehavioralSignals: Boolean = true,
    val enableHiddenWebView: Boolean = true,
    val enableCloudIdentifier: Boolean = false,
    val apiEndpoint: String = "https://api.usefoil.com",
)
```

| Field                     | Default        | Description                                                                                                                                                                            |
| ------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `publishableKey`          | —              | Required. Must start with `pk_live_` or `pk_test_`. Secret keys (`sk_*`) are rejected at configure-time.                                                                               |
| `enableBehavioralSignals` | `true`         | Zero-config native behavioral capture: app lifecycle, navigation, input, scroll, and motion. Touch-stroke observers are opt-in. Disable if your app has strict motion-sensor policies. |
| `enableHiddenWebView`     | `true`         | Enables the `attach(webView)` handoff so an in-app WebView shares this session.                                                                                                        |
| `enableCloudIdentifier`   | `false`        | Opt-in Android Auto Backup continuity hint. Safe to leave `false`; see Backup configuration below if your app uses custom backup rules.                                                |
| `apiEndpoint`             | production API | Advanced override for local development or private deployments.                                                                                                                        |

Runtime integrity and anti-tamper collection is always enabled. It is part of
the baseline SDK behavior, not an integration option.

### Backup configuration

When `enableCloudIdentifier = true`, the SDK stores a per-install UUID in a
`SharedPreferences` file named `foil_cloud_identifier`. Android Auto
Backup can restore that file for the same Google account after reinstall,
factory reset, or device migration, giving Foil a noisy continuity hint.

Most apps do not need extra setup. If your app declares custom backup rules
with `android:fullBackupContent` or `android:dataExtractionRules`, include the
SDK preferences file:

```xml theme={"dark"}
<!-- res/xml/backup_rules.xml -->
<full-backup-content>
    <!-- your existing rules -->
    <include domain="sharedpref" path="foil_cloud_identifier.xml"/>
</full-backup-content>
```

The value is a random non-PII UUID. It is intentionally not encrypted because
Keystore-protected data cannot be restored from backup.

## Get a session at action time

Call `getSession()` from a coroutine right before the sensitive action — not on app launch.

```kotlin theme={"dark"}
lifecycleScope.launch {
    val handoff = FoilClient.getSession()

    val response = httpClient.post("/api/signup") {
        contentType(ContentType.Application.Json)
        setBody(SignupRequest(
            email = email,
            foil = FoilHandoff(handoff.sessionId, handoff.sealedToken),
        ))
    }
}
```

`SessionHandoff` is a simple data class:

```kotlin theme={"dark"}
data class SessionHandoff(val sessionId: String, val sealedToken: String)
```

* `sessionId` is stable across `getSession()` calls for the life of the client.
* `getSession()` posts the native snapshot, performs a bounded best-effort behavioral flush, and returns the handoff your backend should verify.
* The SDK never surfaces verdicts, scores, or visitor IDs to the device — verify on your server.

## Behavioral capture

When `enableBehavioralSignals` is true, the SDK starts zero-config Android behavioral capture automatically. It records the native equivalents of the browser's behavioral events: app lifecycle, hashed screen navigation, viewport and scroll, form focus and input, selection and clipboard, and motion.

No raw form text, raw hints, or raw view identifiers are sent. Field identity is hashed, and geometry is rounded to the active window's coordinate space so the dashboard can show timing and replay context without collecting user-entered values.

Touch-stroke dynamics are still opt-in per screen for higher-fidelity gesture data. Call `observeTouches` when the view is available — typically from `onResume`.

```kotlin theme={"dark"}
override fun onResume() {
    super.onResume()
    FoilClient.observeTouches(rootView, contextId = "checkout")
}
```

`observeTouches` wraps any existing `View.OnTouchListener` on the view; it does not consume events, so your gesture and scroll handlers keep working unchanged.

## WebView correlation

If your app hosts Foil-protected web content in a `WebView`, attach it so the web SDK reuses the native session instead of creating its own.

```kotlin theme={"dark"}
val webView = WebView(context)
FoilClient.attach(webView)
webView.loadUrl("https://your-app.example.com/signup")
```

The native bridge exposes the session handoff as `window.__FOIL_NATIVE__`, which the browser SDK picks up when loaded from within the WebView.

## Native identity and attestation

Native visitor continuity is resolved server-side from the SDK's encrypted observations. `install_id` tracks the app install, `device_id` tracks the device continuity layer, and native `visitor_id` represents the same device within your organization. None of those identifiers are exposed to the app.

Android Key Attestation is optional and positive-only by default. When hardware-backed attestation is available, Foil can use SDK-managed Android Keystore keys to strengthen native identity. When it is unsupported, unavailable, unconfigured, or fails verification, Foil falls back to the other native continuity signals unless your organization explicitly enables strict attestation.

<Note>
  To verify Android Key Attestation in production, add your Android app identity in the Foil dashboard. Baseline native continuity still works without this setup, but hardware-backed attestation will not upgrade identity confidence until the app identity is configured.
</Note>

## API reference

| Method                                            | Description                                                                                                                  |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `FoilClient.configure(context, configuration)`    | Validates the publishable key and starts the runtime. Safe to call once at app start. Calling again reconfigures the client. |
| `suspend FoilClient.getSession(): SessionHandoff` | Posts the snapshot, performs a bounded best-effort behavioral flush, and returns a sealed handoff.                           |
| `suspend FoilClient.waitForFingerprint()`         | Resolves when the durable visitor fingerprint is ready. Optional — `getSession()` works without it.                          |
| `FoilClient.observeTouches(view, contextId)`      | Attaches a non-consuming touch observer to the view. Call once per screen.                                                   |
| `FoilClient.attach(webView)`                      | Shares the current session with a hosted `WebView`.                                                                          |
| `FoilClient.destroy()`                            | Stops timers and releases resources. Rarely needed outside tests.                                                            |

## Optional permissions

The SDK itself declares zero permissions. Your app needs `android.permission.INTERNET` to talk to the Foil API. Optional host-app permissions can unlock additional direct OS facts, but they are not required for verdicts or native continuity.

| Permission                                                   | Unlocks                      | Play Store friction |
| ------------------------------------------------------------ | ---------------------------- | ------------------- |
| `com.google.android.providers.gsf.permission.READ_GSERVICES` | Google Services Framework ID | None.               |

Foil does not collect advertising IDs, serial numbers, IMEI/MEID, raw MAC addresses, SSID/BSSID, precise location, raw proxy hostnames, or broad installed-app inventories. Android package and tooling checks are bounded to fixed allowlists and are used as soft corroboration, not standalone identity anchors.

## Error handling

Every SDK-produced error is a subclass of the sealed `FoilError` type.

```kotlin theme={"dark"}
sealed class FoilError(
    val code: String,
    message: String,
    cause: Throwable? = null,
    val retryable: Boolean = false,
) : Exception(message, cause)
```

| Code                             | Retryable | When it happens                                                                                         |
| -------------------------------- | --------- | ------------------------------------------------------------------------------------------------------- |
| `config.invalid_publishable_key` | no        | `publishableKey` is missing, uses the `sk_*` secret prefix, or doesn't match `pk_live_*` / `pk_test_*`. |
| `client.not_configured`          | no        | A client method was called before `configure()`.                                                        |
| `session.create_failed`          | varies    | Session handshake with the Foil API failed. The `retryable` flag reflects the underlying cause.         |
| `session.handshake_expired`      | no        | The server expired the session. Call `configure()` again to mint a new one.                             |
| `session.invalid_or_expired`     | no        | The active session is invalid or expired.                                                               |
| `transport.batch_post_failed`    | varies    | A signal batch upload failed.                                                                           |
| `transport.rate_limited`         | yes       | HTTP 429 from the API. Back off and retry.                                                              |
| `transport.upgrade_required`     | no        | HTTP 410/426. The SDK version is too old — upgrade the app.                                             |
| `transport.network`              | yes       | DNS, TLS, or connectivity failure.                                                                      |
| `crypto.failure`                 | no        | A crypto primitive failed. Usually a platform issue worth reporting.                                    |
| `internal`                       | no        | Unexpected state. File a support ticket with the message and cause.                                     |

The `code` values are stable. Category prefixes (`config.*`, `transport.*`) are safe to branch on with a wildcard — new codes within a category keep the same retry semantics.

### Fallback policy

If Foil fails, your app should still work. Log the error and continue without a handoff; treat the missing server-side signal according to the sensitivity of the action.

```kotlin theme={"dark"}
suspend fun foilHandoff(): SessionHandoff? =
    try {
        FoilClient.getSession()
    } catch (err: FoilError) {
        if (err.retryable) {
            runCatching { FoilClient.getSession() }.getOrNull()
        } else {
            reportError(err)
            null
        }
    }
```

See [Going to production](/going-to-production) for guidance on fall-open vs. fall-closed policy.

## Best practices

* **Configure in `Application.onCreate()`** so signals are collecting before any screen opens.
* **Call `getSession()` late** — right before the sensitive action, inside a coroutine.
* **Wrap errors** — degrade gracefully when the SDK can't produce a handoff.
* **Reuse the singleton** — `FoilClient` is a process-wide singleton. Do not hold per-screen copies.

## What's next

<CardGroup cols={2}>
  <Card title="Server verification" icon="shield-check" href="/server-verification">
    Verify the sealed token on your backend
  </Card>

  <Card title="Browser SDK" icon="monitor" href="/browser-sdk">
    Equivalent guide for the web surface
  </Card>

  <Card title="iOS SDK" icon="apple" href="/ios-sdk">
    Native iOS integration
  </Card>

  <Card title="Going to production" icon="rocket" href="/going-to-production">
    Rollout checklist and monitoring
  </Card>
</CardGroup>
