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

# Android Reference

> The full API signatures, models and error definitions of the OneS1ght Android SDK.

<Info>
  This document targets **v0.0.1**. You can check the running version with `OneS1ght.SDK_VERSION`.
</Info>

`OneS1ght` is the single `object` entry point for the whole app — you never create an instance and call every API on it
directly. Asynchronous APIs come in **two forms with the same name** — a `suspend fun` for Kotlin, and a form that takes a
`Callback<T>` as its last argument for Java. The tables below use the Kotlin form.

```kotlin theme={null}
import co.onecheck.ones1ght.android.*          // OneS1ght · FloorSession · SdkError · listeners
import co.onecheck.ones1ght.android.model.*    // Building · Floor · Zone · Coordinates · Trigger · ConfigChange
```

## Lifecycle

| API                                                                         | Description                                                                                                                                                                                |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `suspend initialize(context: Context, sdkKey: String, baseUrl: String = …)` | Once at app start. Key validation → settings load. Idempotent — a repeat call after success is ignored, after failure it retries, and **a call with a different key rebuilds the session** |
| `suspend permissions(activity: ComponentActivity): PermissionStatus`        | Checks/requests positioning permissions (`RANGING` + precise location) — **shows the system dialog when needed**. Can be called before `initialize`                                        |
| `suspend reset()`                                                           | Discards the session. You can then `initialize` with a different key (for runtime key rotation)                                                                                            |
| `setLanguage(code: String?)`                                                | Language of SDK logs and messages — `"ko"`·`"ja"`·`"en"`. `null` uses the device language (default)                                                                                        |

`initialize` parameters:

| Parameter | Type                                    | Description                                                                                                                                               |
| --------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `context` | `Context`                               | App Context. The SDK keeps only the `applicationContext`                                                                                                  |
| `sdkKey`  | `String`                                | Issued by the OneS1ght console (`ock_sdk_…`). **This is the only key the app passes** — the SDK fetches every other key positioning needs from the server |
| `baseUrl` | `String` (production server by default) | Only for customers running their own server. Production vs. development is decided by the **key**, not this argument                                      |

The default `baseUrl` is `https://console.ones1ght.com/api/sdk/v1`.

<Warning>
  **`initialize` doesn't look up buildings or floors, and doesn't check device support.** Unsupported devices still pass
  initialization (plans and zones can be read); only starting positioning (`begin()`) is rejected. Space selection is `setFloorMap`'s job.
</Warning>

## Positioning session — FloorSession

```kotlin theme={null}
val session = OneS1ght.floorSession()
```

| API                                            | Description                                                                                                                                                                         |
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `floorSession(): FloorSession`                 | Gets the session. **Always the same instance** — one UWB radio, decision engine and coordinate buffer per device. Throws `SdkError.NotInitialized` if `initialize` was never called |
| `session.begin()`                              | Starts positioning (on entering the store). `suspend`                                                                                                                               |
| `session.begin(provider: PositioningProvider)` | Injects a custom positioning source — for tests and demos (`MockPositioningProvider`). Supplies coordinates only; zone callbacks don't fire                                         |
| `session.end()`                                | Stops positioning + uploads the remaining coordinates. Initialization and floor are kept → restart by calling `begin` again. `suspend`                                              |
| `session.pause()`                              | **Pause** — stops only position display, upload and zone decisions; **the engine keeps running**, so resuming is immediate                                                          |
| `session.resume()`                             | Ends the pause. Clears the decision state and continues                                                                                                                             |
| `session.floor: Floor?`                        | The currently selected floor                                                                                                                                                        |
| `session.isRunning: Boolean`                   | Whether positioning is running                                                                                                                                                      |
| `session.isPaused: Boolean`                    | Whether it's paused. `begin`/`end` don't carry over this state                                                                                                                      |

## Callbacks

Session callbacks go on the `FloorSession` instance; the debug log goes on `OneS1ght`. All callbacks are called on the
**main thread**. Listeners are `fun interface`s — use `ZoneListener { zone -> … }` in Kotlin and lambdas in Java.

| Callback                  | Listener                                       | Description                                                                                     |
| ------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `session.onPosition`      | `PositionListener` — `(Coordinates)`           | Live coordinates (local floor-plan meters)                                                      |
| `session.onZoneEnter`     | `ZoneListener` — `(Zone)`                      | Zone entered — decided on the device immediately (no server round-trip)                         |
| `session.onZoneExit`      | `ZoneListener` — `(Zone)`                      | Zone exited                                                                                     |
| `session.onZoneDwell`     | `DwellListener` — `(Zone, Double)`             | Dwell — (zone, seconds dwelled). Once per zone                                                  |
| `session.onTriggers`      | `TriggersListener` — `(String, List<Trigger>)` | (zoneId, triggers) — personalized actions **matched by the server**                             |
| `session.onConfigChanged` | `ConfigChangeListener` — `(ConfigChange)`      | Notifies when zones, plans or campaigns change in the console — attached only while positioning |
| `OneS1ght.onDebugLog`     | `DebugLogListener` — `(LogLevel, String)`      | The SDK's internal activity log. Register before `initialize` to catch the initialization logs  |

### ConfigChange — console changes

| Case                                 | Meaning                                                 | Recommended response                          |
| ------------------------------------ | ------------------------------------------------------- | --------------------------------------------- |
| `ZonesChanged(floorId)`              | Zones were created, changed or removed                  | `refreshZones()` (coalesce bursts, \~1 s)     |
| `PlanChanged(floorId)`               | The floor plan changed                                  | Redraw the plan                               |
| `RulesChanged(zoneId)`               | A campaign's state changed                              | Re-read the events of the zone the user is in |
| `SdkConfigChanged(rateHz, logLevel)` | Remote settings changed                                 | —                                             |
| `ResyncNeeded`                       | The connection was re-established or events were missed | Reload everything you use                     |

### LogLevel — log levels

```kotlin theme={null}
enum class LogLevel { LOG, INFO, WARN, ERROR }   // LOG < INFO < WARN < ERROR
```

Import it with `import co.onecheck.ones1ght.android.runtime.LogLevel`. Values compare in declaration order, so you can
filter with `level >= LogLevel.WARN`.

| Level   | Meaning                                                       |
| ------- | ------------------------------------------------------------- |
| `LOG`   | Flow record — you normally don't need to look at it           |
| `INFO`  | Good to know — normal, but helpful when it stands out         |
| `WARN`  | Needs checking — not broken, but won't work as expected as-is |
| `ERROR` | Broken — the feature won't work unless you fix it             |

<Warning>
  Only `onTriggers` is a server response — if the network drops, zone entries still arrive but triggers don't.
</Warning>

## Space lookup

One method per endpoint, with lists and single items in pairs. All are `suspend`.

| API                                            | Description                                                                |
| ---------------------------------------------- | -------------------------------------------------------------------------- |
| `buildings(): List<Building>`                  | Building list. Empty if no space is connected in the console               |
| `building(buildingId): Building`               | A single building                                                          |
| `floors(buildingId): List<Floor>`              | Floor list — **no plan image** (`image == null`, to keep the list light)   |
| `floor(buildingId, floorId): Floor`            | A single floor — with the plan image (served from cache, no extra request) |
| `zones(buildingId, floorId): List<Zone>`       | The floor's zones                                                          |
| `zone(buildingId, floorId, zoneId): Zone`      | A single zone                                                              |
| `locators(buildingId, floorId): FloorLocators` | The floor's locator placement and UWB session ID                           |

## Floor selection

| API                                                              | Description                                                                                                                                                                                                              |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `suspend setFloorMap(floor: Floor?, buildingId: String? = null)` | Selects the floor — loads locators, session and zones into the positioning pipeline. While running it **switches floors immediately** (session kept). `null` clears it. Omitting `buildingId` uses the previous building |
| `suspend refreshZones(): List<Zone>`                             | Re-reads only the current floor's zones (lightweight — no plan re-download). Applied to decisions immediately. Doesn't throw on failure; returns the zones it already has                                                |

<Warning>
  Android doesn't detect the floor automatically. **Without `setFloorMap`, no coordinates are produced** (`E3001`, WARN).
</Warning>

## Profiles

| API                                                              | Description                                                                                               |
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `suspend createProfile(attributes: Map<String, String>): String` | Creates a profile — returns the server-issued `profileId`. **Store and reuse it in the app**              |
| `suspend getProfile(profileId): Map<String, String>`             | Reads the attributes                                                                                      |
| `suspend putProfile(profileId, attributes)`                      | **Replaces all** attributes (not a partial update)                                                        |
| `suspend deleteProfile(profileId)`                               | Deletes the profile                                                                                       |
| `identify(profileId: String?)`                                   | Connects — required before positioning, **after `initialize`**. `null` on logout. Call on the main thread |

<Note>
  Your member IDs never reach the server — only you keep that mapping.
</Note>

## Data upload

Coordinates are uploaded at **300 points or 60 seconds**, whichever comes first.
Remaining coordinates are also sent when the app goes to the background and on `end()`.

| API              | Description                                                  |
| ---------------- | ------------------------------------------------------------ |
| `suspend send()` | Uploads the buffer right now                                 |
| `empty()`        | **Discards** the buffer — no upload. Call on the main thread |

## State

| API                  | Type                 | Description                                                                                                         |
| -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `isInitialized`      | `Boolean`            | Whether initialization succeeded (valid key + settings loaded)                                                      |
| `deviceAvailability` | `DeviceAvailability` | Whether positioning is possible + why not. No network use. **Read after `initialize`**                              |
| `isDeviceAvailable`  | `Boolean`            | Short form of the above (`== AVAILABLE`)                                                                            |
| `googleMapKey`       | `String?`            | The Google Maps key provided by the console — for apps that draw their own map. `null` before `initialize` succeeds |
| `SDK_VERSION`        | `String`             | SDK version (e.g. `"0.0.1"`)                                                                                        |

From Java, read these through getters — `OneS1ght.isInitialized()`, `OneS1ght.getDeviceAvailability()`, `OneS1ght.getGoogleMapKey()`.

```kotlin theme={null}
enum class DeviceAvailability {
    AVAILABLE,             // positioning available
    OS_VERSION_TOO_LOW,    // below Android 17 (API 37) — "available after an OS update"
    DEVICE_NOT_SUPPORTED,  // device without UWB DL-TDoA support
}

enum class PermissionStatus {
    AUTHORIZED,            // positioning can start
    DENIED,                // denied (including approximate-only and no answer in 30 s) — send users to Settings
    UNSUPPORTED,           // positioning is impossible on this device/OS
}
```

## Errors — SdkError

`SdkError` covers the 5 errors the SDK itself throws. Communication failures arrive as `ApiError`
(`co.onecheck.ones1ght.android.network`). Both are `Exception` subclasses, and `.code.code` gives the
[error code](/en/sdk/faq/error-code) string.

<Tabs>
  <Tab title="Kotlin">
    ```kotlin theme={null}
    try {
        OneS1ght.initialize(context = applicationContext, sdkKey = key)
    } catch (e: SdkError) {
        Log.w("OneS1ght", e.code.code)                   // "E1003"
    } catch (e: ApiError) {
        Log.w("OneS1ght", "${e.code.code} ${e.message}") // "E5005 …"
    }
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    OneS1ght.initialize(context, key, new Callback<Void>() {
        @Override public void onSuccess(Void result) { }
        @Override public void onError(Throwable error) {
            if (error instanceof SdkError) {
                Log.w("OneS1ght", ((SdkError) error).getCode().getCode());   // "E1003"
            } else if (error instanceof ApiError) {
                Log.w("OneS1ght", ((ApiError) error).getCode().getCode() + " " + error.getMessage());
            }
        }
    });
    ```
  </Tab>
</Tabs>

| Class                          | Code    | When                                       | Action                                      |
| ------------------------------ | ------- | ------------------------------------------ | ------------------------------------------- |
| `SdkError.NotInitialized`      | `E1001` | Another API called without `initialize`    | Fix the call order                          |
| `SdkError.NotIdentified`       | `E1004` | Positioning started without `identify`     | `identify(profileId)` right after sign-in   |
| `SdkError.PositioningDisabled` | `E1003` | Key is valid but positioning is turned off | Check console settings · contact support    |
| `SdkError.OsVersionTooLow`     | `E2001` | Below Android 17 (API 37)                  | Ask the user to update the OS               |
| `SdkError.DeviceNotSupported`  | `E2002` | Device without UWB DL-TDoA                 | Branch beforehand with `deviceAvailability` |
| `ApiError.InvalidKey`          | `E1002` | Key is wrong or revoked (401)              | Check the key in the console · reissue      |
| `ApiError.Forbidden`           | `E5004` | Another tenant's resource (403)            | Check the key scope                         |
| `ApiError.NotFound`            | `E5003` | Target not found (404)                     | Usually a contract mismatch                 |
| `ApiError.Unprocessable`       | `E5003` | Payload problem (422)                      | Suspect an SDK/server version mismatch      |
| `ApiError.Server`              | `E5002` | Server 5xx                                 | Check with your administrator               |
| `ApiError.Network`             | `E5001` | Offline · timeout                          | Retried automatically                       |
| `ApiError.Decoding`            | `E5005` | Response JSON shape mismatch               | The reason is in `message`                  |

Positioning runtime (`E4001`–`E4004`), space setup (`E3001`–`E3006`) and permission (`E2003`) errors aren't thrown —
they're **only logged**. Check them in `onDebugLog` or the console log analyzer. [Full list](/en/sdk/faq/error-code)

## Models

Models live in the `co.onecheck.ones1ght.android.model` package.

### Coordinates — live coordinates

```kotlin theme={null}
data class Coordinates(
    val x: Double,   // meters
    val y: Double,   // meters
    val z: Double,   // height (0 for 2D positioning)
)
```

### Building · Floor

| Type       | Field                             | Description                                                |
| ---------- | --------------------------------- | ---------------------------------------------------------- |
| `Building` | `id` · `name`                     | Building ID · name                                         |
|            | `floorCount: Int?`                | Number of floors (`null` if the server doesn't provide it) |
| `Floor`    | `id` · `name`                     | Floor ID · name                                            |
|            | `image: ByteArray?`               | Plan PNG — `null` from `floors()`, filled from `floor()`   |
|            | `hasPlan: Boolean`                | Whether a plan is registered                               |
|            | `originX` · `originY`             | Plan origin offset (meters)                                |
|            | `widthM` · `heightM`              | Real plan size (meters)                                    |
|            | `minX` · `minY` · `maxX` · `maxY` | Placement bounds (derived from origin + size)              |

### Locator · FloorLocators

| Type            | Field                       | Description                                                        |
| --------------- | --------------------------- | ------------------------------------------------------------------ |
| `Locator`       | `address: Int`              | Last 2 bytes of the UWB MAC (e.g. `0x9DD7`)                        |
|                 | `x` · `y` · `z`             | Local floor-plan meters                                            |
| `FloorLocators` | `locators: List<Locator>`   | Locators installed on this floor                                   |
|                 | `sessionId: Int?`           | UWB session — differs per floor. Positioning can't start if `null` |
|                 | `positioningReady: Boolean` | Whether there are locators and a session (derived)                 |

### Zone

| Field                         | Type             | Description                                                        |
| ----------------------------- | ---------------- | ------------------------------------------------------------------ |
| `id` / `name`                 | `String`         | Zone identifier · name                                             |
| `polygon`                     | `List<Position>` | Vertices (in order) — `Position` is local floor-plan meters (x, y) |
| `inDist`                      | `Double`         | Entry distance (m) — default 3.0                                   |
| `inCount` / `inCountInterval` | `Int`            | Detections needed to confirm entry · count interval (s)            |
| `outPeriod`                   | `Int`            | Grace period before exit                                           |
| `priority`                    | `Int`            | Priority when zones overlap                                        |
| `callInout`                   | `Boolean`        | Whether enter/exit callbacks are issued                            |
| `dwellSeconds`                | `Int?`           | Dwell threshold (s) — no dwell event when `null` or 0              |

Decision parameters are set per zone in the console under **Space management → select a zone → SDK zone detection**
([how to set it](/en/locator/areas)). Android decides zones on the device against the zone shapes received from the console.

<Warning>
  If `inCount` is `0`, entry is never confirmed and no zone events fire.
  New zones registered in the console default to `1`; if you have old zones with `0`, check them in the console.
</Warning>

### Trigger — personalized action

| Field       | Type                   | Description                                     |
| ----------- | ---------------------- | ----------------------------------------------- |
| `triggerId` | `String`               | Trigger identifier                              |
| `type`      | `String`               | `signage · coupon · tracking · merch · generic` |
| `payload`   | `Map<String, String>?` | Type-specific extras                            |

### MockPositioningProvider — for testing

```kotlin theme={null}
class MockPositioningProvider : PositioningProvider {
    fun simulateEnter(buildingId: String)
    fun simulatePosition(c: Coordinates, floorId: String?, atMs: Long)
    fun simulateZone(zoneId: String, status: ZoneEventStatus, floorId: String?, atMs: Long)
}
```

It's in the `co.onecheck.ones1ght.android.positioning` package. Inject it with `session.begin(provider)` to exercise the SDK
pipeline without UWB. `ZoneEventStatus` is `ENTER` · `DWELL` · `EXIT`.

## Threading

* Kotlin `suspend` APIs can be called from any coroutine; the SDK switches threads internally.
* Call `identify`, `empty`, `pause` and `resume` on the **main thread**.
* Callbacks (`onPosition`, …) and Java `Callback` results are delivered on the main thread — it's safe to update the UI directly.

## Constraints to know

<Warning>
  UWB positioning on Android is **foreground-only**. When the app goes to the background, positioning stops and the
  remaining coordinates are uploaded; it restarts when the app returns. This is a platform constraint the SDK can't work around.
</Warning>

<Warning>
  The coordinate buffer lives in memory. If the app is force-quit, coordinates not yet uploaded are lost (`E5006`).
</Warning>
