> ## 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 레퍼런스

> OneS1ght Android SDK의 전체 API 시그니처, 모델, 에러 정의입니다.

<Info>
  이 문서는 **v0.0.1** 기준입니다. 판별 값은 `OneS1ght.SDK_VERSION` 으로 확인할 수 있습니다.
</Info>

`OneS1ght` 는 앱 전체에 하나뿐인 `object` 진입점입니다 — 인스턴스를 만들지 않으며 모든 API 를 직접 호출합니다.
비동기 API 는 **같은 이름으로 두 가지 형태**가 있습니다 — Kotlin 은 `suspend fun`, Java 는 마지막 인자에
`Callback<T>` 를 받는 형태입니다. 아래 표는 Kotlin 형태로 적었습니다.

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

## 생명주기

| API                                                                         | 설명                                                                                |
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `suspend initialize(context: Context, sdkKey: String, baseUrl: String = …)` | 앱 시작 시 1회. 키 검증 → 설정 로드. 멱등 — 성공 후 재호출은 무시, 실패 후 재호출은 재시도, **다른 키로 재호출 시 세션 재구성** |
| `suspend permissions(activity: ComponentActivity): PermissionStatus`        | 측위 권한(`RANGING` + 정밀 위치) 확인·요청 — **필요하면 시스템 팝업이 뜹니다**. `initialize` 전에도 호출 가능     |
| `suspend reset()`                                                           | 세션 폐기. 이후 다른 키로 `initialize` 가능 (런타임 키 교체용)                                       |
| `setLanguage(code: String?)`                                                | SDK 로그·안내 문구의 언어 — `"ko"`·`"ja"`·`"en"`. `null` 이면 기기 언어 (기본값)                    |

`initialize` 의 파라미터:

| 파라미터      | 타입                  | 설명                                                                                      |
| --------- | ------------------- | --------------------------------------------------------------------------------------- |
| `context` | `Context`           | 앱 Context. SDK 는 `applicationContext` 만 보관합니다                                           |
| `sdkKey`  | `String`            | OneS1ght 콘솔 발급 (`ock_sdk_…`). **앱이 넘기는 키는 이것 하나뿐**이며, 측위에 필요한 나머지 키는 SDK 가 서버에서 직접 받습니다 |
| `baseUrl` | `String` (기본 운영 서버) | 자체 서버를 구축한 고객만. 운영/개발 구분은 이 인자가 아니라 **키**가 가릅니다                                         |

기본 `baseUrl` 은 `https://console.ones1ght.com/api/sdk/v1` 입니다.

<Warning>
  **`initialize` 는 건물·층을 조회하지 않고, 기기 지원 여부도 확인하지 않습니다.** 미지원 기기도 초기화는
  통과하며(도면·구역 조회 가능), 측위 시작(`begin()`)에서만 거절됩니다. 공간 설정은 `setFloorMap` 의 몫입니다.
</Warning>

## 측위 세션 — FloorSession

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

| API                                            | 설명                                                                                                           |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `floorSession(): FloorSession`                 | 세션 획득. **항상 같은 인스턴스** — UWB 라디오·판정 엔진·좌표 버퍼가 기기당 하나뿐. `initialize` 를 한 번도 부르지 않았으면 `SdkError.NotInitialized` |
| `session.begin()`                              | 측위 시작 (매장 진입 시). `suspend`                                                                                   |
| `session.begin(provider: PositioningProvider)` | 커스텀 측위 주입 — 테스트·데모용(`MockPositioningProvider`). 좌표만 공급하며 구역 콜백은 발생하지 않습니다                                    |
| `session.end()`                                | 측위 정지 + 남은 좌표 전송. 초기화·층 설정은 유지 → `begin` 재호출로 재시작 가능. `suspend`                                              |
| `session.pause()`                              | **일시정지** — 좌표 표시·전송·구역 판정만 멈추고 **엔진은 계속 돌립니다.** 재개가 즉시 이어집니다                                                 |
| `session.resume()`                             | 일시정지 해제. 판정기를 비우고 이어서 받습니다                                                                                   |
| `session.floor: Floor?`                        | 현재 지정된 층                                                                                                     |
| `session.isRunning: Boolean`                   | 측위 가동 여부                                                                                                     |
| `session.isPaused: Boolean`                    | 일시정지 여부. `begin`·`end` 는 이 상태를 남기지 않습니다                                                                      |

## 콜백

세션 콜백은 `FloorSession` 인스턴스에, 디버그 로그는 `OneS1ght` 에 답니다. 모든 콜백은 **메인 스레드**에서 호출됩니다.
리스너는 `fun interface` 라 Kotlin 에서는 `ZoneListener { zone -> … }`, Java 에서는 람다로 등록합니다.

| 콜백                        | 리스너                                            | 설명                                                     |
| ------------------------- | ---------------------------------------------- | ------------------------------------------------------ |
| `session.onPosition`      | `PositionListener` — `(Coordinates)`           | 실시간 좌표 (도면 로컬 미터)                                      |
| `session.onZoneEnter`     | `ZoneListener` — `(Zone)`                      | 구역 진입 — 온디바이스 판정 즉시 (서버 왕복 없음)                         |
| `session.onZoneExit`      | `ZoneListener` — `(Zone)`                      | 구역 이탈                                                  |
| `session.onZoneDwell`     | `DwellListener` — `(Zone, Double)`             | 체류 발화 — (구역, 체류 초). 구역당 1회                             |
| `session.onTriggers`      | `TriggersListener` — `(String, List<Trigger>)` | (zoneId, 트리거 목록) — **서버가 매칭**한 개인화 액션                  |
| `session.onConfigChanged` | `ConfigChangeListener` — `(ConfigChange)`      | 콘솔에서 구역·도면·시책이 바뀌면 통지 — 측위 중에만 붙어 있습니다                 |
| `OneS1ght.onDebugLog`     | `DebugLogListener` — `(LogLevel, String)`      | SDK 내부 활동 로그. `initialize` 보다 먼저 등록해야 초기화 로그를 놓치지 않습니다 |

### ConfigChange — 콘솔 변경

| 케이스                                  | 의미                   | 권장 대응                               |
| ------------------------------------ | -------------------- | ----------------------------------- |
| `ZonesChanged(floorId)`              | 구역이 생기거나 바뀌거나 사라짐    | `refreshZones()` (연속 수신은 1초 정도 모아서) |
| `PlanChanged(floorId)`               | 층 도면이 바뀜             | 도면 다시 그리기                           |
| `RulesChanged(zoneId)`               | 시책 상태가 바뀜            | 들어가 있는 구역의 이벤트를 다시 조회               |
| `SdkConfigChanged(rateHz, logLevel)` | 원격 설정이 바뀜            | —                                   |
| `ResyncNeeded`                       | 연결이 다시 맺어졌거나 이벤트를 놓침 | 쓰고 있는 정보를 통째로 다시 조회                 |

### LogLevel — 로그 등급

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

`import co.onecheck.ones1ght.android.runtime.LogLevel` 로 가져옵니다. 선언 순서대로 비교할 수 있어
`level >= LogLevel.WARN` 처럼 걸러낼 수 있습니다.

| 등급      | 뜻                                        |
| ------- | ---------------------------------------- |
| `LOG`   | 흐름 기록 — 평상시엔 안 봐도 됩니다                    |
| `INFO`  | 알아두면 좋은 것 — 정상이지만 눈에 띄면 도움이 됩니다          |
| `WARN`  | 확인이 필요한 것 — 고장은 아니지만 이대로면 기대한 대로 안 돌아갑니다 |
| `ERROR` | 고장 — 손대지 않으면 그 기능이 동작하지 않습니다             |

<Warning>
  `onTriggers` 만 서버 응답입니다 — 네트워크가 끊기면 진입 판정은 오지만 트리거는 오지 않습니다.
</Warning>

## 공간 조회

엔드포인트 하나당 메서드 하나이며, 목록과 단건이 짝을 이룹니다. 모두 `suspend` 입니다.

| API                                            | 설명                                                |
| ---------------------------------------------- | ------------------------------------------------- |
| `buildings(): List<Building>`                  | 건물 목록. 콘솔에 연결된 공간이 없으면 빈 목록                       |
| `building(buildingId): Building`               | 건물 하나                                             |
| `floors(buildingId): List<Floor>`              | 층 목록 — **도면 이미지 없음**(`image == null`, 목록을 가볍게 유지) |
| `floor(buildingId, floorId): Floor`            | 층 하나 — 도면 이미지 포함 (캐시에서 나와 요청이 늘지 않음)              |
| `zones(buildingId, floorId): List<Zone>`       | 층의 구역 목록                                          |
| `zone(buildingId, floorId, zoneId): Zone`      | 구역 하나                                             |
| `locators(buildingId, floorId): FloorLocators` | 층의 로케이터 배치와 UWB 세션 ID                             |

## 층 지정

| API                                                              | 설명                                                                                                  |
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `suspend setFloorMap(floor: Floor?, buildingId: String? = null)` | 층 지정 — 로케이터·세션·구역을 측위 파이프라인에 주입. 가동 중이면 **즉시 층 전환**(세션 유지). `null` 이면 해제. `buildingId` 를 생략하면 직전 건물 |
| `suspend refreshZones(): List<Zone>`                             | 현재 층의 구역만 재조회 (경량 — 도면 재다운로드 없음). 판정에 즉시 반영. 실패해도 던지지 않고 지금 가진 구역을 돌려줌                              |

<Warning>
  Android 는 층을 자동으로 인식하지 않습니다. **`setFloorMap` 을 부르지 않으면 좌표가 나오지 않습니다**(`E3001`, WARN).
</Warning>

## 프로필

| API                                                              | 설명                                                              |
| ---------------------------------------------------------------- | --------------------------------------------------------------- |
| `suspend createProfile(attributes: Map<String, String>): String` | 프로필 생성 — 서버가 발급한 `profileId` 반환. **앱이 보관해 재사용**                 |
| `suspend getProfile(profileId): Map<String, String>`             | 속성 조회                                                           |
| `suspend putProfile(profileId, attributes)`                      | 속성 **전체 교체** (부분 갱신 아님)                                         |
| `suspend deleteProfile(profileId)`                               | 프로필 삭제                                                          |
| `identify(profileId: String?)`                                   | 연결 — 측위 전에 반드시, **`initialize` 뒤에**. 로그아웃 시 `null`. 메인 스레드에서 호출 |

<Note>
  고객사 회원 ID 는 서버로 오지 않습니다 — 매핑은 고객사만 보관합니다.
</Note>

## 데이터 전송

좌표는 **300건 또는 60초** 중 먼저 도달하는 쪽에서 전송됩니다.
백그라운드 진입 시와 `end()` 시에도 남은 좌표를 보냅니다.

| API              | 설명                                 |
| ---------------- | ---------------------------------- |
| `suspend send()` | 버퍼를 지금 즉시 전송                       |
| `empty()`        | 버퍼를 **버린다** — 전송하지 않음. 메인 스레드에서 호출 |

## 상태 조회

| API                  | 타입                   | 설명                                                                 |
| -------------------- | -------------------- | ------------------------------------------------------------------ |
| `isInitialized`      | `Boolean`            | 초기화 성공 여부 (키 유효 + 설정 로드)                                           |
| `deviceAvailability` | `DeviceAvailability` | 측위 가능 여부 + 불가 사유. 네트워크 사용 없음. **`initialize` 다음에 읽기**              |
| `isDeviceAvailable`  | `Boolean`            | 위의 요약형 (`== AVAILABLE`)                                            |
| `googleMapKey`       | `String?`            | 콘솔이 내려준 Google Maps 키 — 앱이 자체 지도를 그릴 때. `initialize` 성공 전에는 `null` |
| `SDK_VERSION`        | `String`             | SDK 버전 (예: `"0.0.1"`)                                              |

Java 에서는 getter 로 읽습니다 — `OneS1ght.isInitialized()`, `OneS1ght.getDeviceAvailability()`, `OneS1ght.getGoogleMapKey()`.

```kotlin theme={null}
enum class DeviceAvailability {
    AVAILABLE,             // 측위 가능
    OS_VERSION_TOO_LOW,    // Android 17(API 37) 미만 — "OS 업데이트 후 사용 가능" 안내
    DEVICE_NOT_SUPPORTED,  // UWB DL-TDoA 미지원 기기
}

enum class PermissionStatus {
    AUTHORIZED,            // 측위를 시작할 수 있다
    DENIED,                // 거부(대략 위치만 허용·30초 무응답 포함) — 설정 앱으로 안내
    UNSUPPORTED,           // 이 기기·OS 에서는 측위 자체가 불가
}
```

## 에러 — SdkError

`SdkError` 는 SDK 자체가 던지는 5가지입니다. 통신 실패는 `ApiError`(`co.onecheck.ones1ght.android.network`)로 옵니다.
두 타입 모두 `Exception` 하위 클래스이며 `.code.code` 로 [에러 코드](/sdk/faq/error-code) 문자열을 꺼낼 수 있습니다.

<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>

| 클래스                            | 코드      | 발생 시점                     | 조치                           |
| ------------------------------ | ------- | ------------------------- | ---------------------------- |
| `SdkError.NotInitialized`      | `E1001` | `initialize` 없이 다른 API 호출 | 호출 순서 수정                     |
| `SdkError.NotIdentified`       | `E1004` | `identify` 없이 측위 시작       | 인증 직후 `identify(profileId)`  |
| `SdkError.PositioningDisabled` | `E1003` | 키는 유효하나 측위 기능 꺼짐          | 콘솔 설정 확인 · 지원팀 문의            |
| `SdkError.OsVersionTooLow`     | `E2001` | Android 17(API 37) 미만     | OS 업데이트 안내                   |
| `SdkError.DeviceNotSupported`  | `E2002` | UWB DL-TDoA 미지원 기기        | `deviceAvailability` 로 사전 분기 |
| `ApiError.InvalidKey`          | `E1002` | 키가 잘못됐거나 폐기됨 (401)        | 콘솔에서 키 상태 확인·재발급             |
| `ApiError.Forbidden`           | `E5004` | 다른 테넌트 자원 (403)           | 키 스코프 확인                     |
| `ApiError.NotFound`            | `E5003` | 대상 없음 (404)               | 대개 계약 불일치                    |
| `ApiError.Unprocessable`       | `E5003` | 페이로드 문제 (422)             | SDK·서버 버전 불일치 의심             |
| `ApiError.Server`              | `E5002` | 서버 5xx                    | 통합 관리자 확인                    |
| `ApiError.Network`             | `E5001` | 오프라인·타임아웃                 | 자동 재시도                       |
| `ApiError.Decoding`            | `E5005` | 응답 JSON 형태 불일치            | `message` 에 사유가 담깁니다         |

측위 런타임(`E4001`~~`E4004`)과 공간 설정(`E3001`~~`E3006`), 권한(`E2003`)은 throw 되지 않고 **로그로만** 남습니다 —
`onDebugLog` 또는 콘솔 로그 분석기에서 확인하세요. [전체 목록](/sdk/faq/error-code)

## 모델

모델은 `co.onecheck.ones1ght.android.model` 패키지에 있습니다.

### Coordinates — 실시간 좌표

```kotlin theme={null}
data class Coordinates(
    val x: Double,   // 미터
    val y: Double,   // 미터
    val z: Double,   // 높이 (2D 측위면 0)
)
```

### Building · Floor — 건물·층

| 타입         | 필드                                | 설명                                                    |
| ---------- | --------------------------------- | ----------------------------------------------------- |
| `Building` | `id` · `name`                     | 건물 ID · 이름                                            |
|            | `floorCount: Int?`                | 층 수 (서버가 주지 않으면 `null`)                               |
| `Floor`    | `id` · `name`                     | 층 ID · 이름                                             |
|            | `image: ByteArray?`               | 도면 PNG — `floors()` 로 받으면 `null`, `floor()` 로 받으면 채워짐 |
|            | `hasPlan: Boolean`                | 도면 등록 여부                                              |
|            | `originX` · `originY`             | 도면 원점 오프셋 (미터)                                        |
|            | `widthM` · `heightM`              | 도면 실제 크기 (미터)                                         |
|            | `minX` · `minY` · `maxX` · `maxY` | 배치 범위 (원점 + 크기에서 파생)                                  |

### Locator · FloorLocators — 로케이터

| 타입              | 필드                          | 설명                                  |
| --------------- | --------------------------- | ----------------------------------- |
| `Locator`       | `address: Int`              | UWB MAC 뒤 2바이트 (예: `0x9DD7`)        |
|                 | `x` · `y` · `z`             | 도면 로컬 미터                            |
| `FloorLocators` | `locators: List<Locator>`   | 이 층에 설치된 로케이터                       |
|                 | `sessionId: Int?`           | UWB 세션 — 층마다 다름. `null` 이면 측위 시작 불가 |
|                 | `positioningReady: Boolean` | 로케이터가 있고 세션도 있는가 (파생값)              |

### Zone — 구역

| 필드                            | 타입               | 설명                                        |
| ----------------------------- | ---------------- | ----------------------------------------- |
| `id` / `name`                 | `String`         | 구역 식별자 · 이름                               |
| `polygon`                     | `List<Position>` | 꼭짓점 (순서대로) — `Position` 은 도면 로컬 미터 (x, y) |
| `inDist`                      | `Double`         | 진입 판단 거리(m) — 기본 3.0                      |
| `inCount` / `inCountInterval` | `Int`            | 진입 확정 감지 횟수 · 카운트 간격(초)                   |
| `outPeriod`                   | `Int`            | 이탈 판정 유예                                  |
| `priority`                    | `Int`            | 영역이 겹칠 때 우선순위                             |
| `callInout`                   | `Boolean`        | 진출입 콜백 발행 여부                              |
| `dwellSeconds`                | `Int?`           | 체류 발화 기준(초) — `null` 또는 0 이면 체류 이벤트 없음    |

판정 파라미터는 콘솔 **공간 관리 → 구역 선택 → SDK 영역판정**에서 구역마다 설정합니다
([설정 방법](/locator/areas)). Android 는 콘솔에서 받은 구역 도형을 기기에서 직접 대조해 판정합니다.

<Warning>
  `inCount` 가 `0` 이면 진입이 영원히 확정되지 않아 구역 이벤트가 발생하지 않습니다.
  콘솔에서 등록한 신규 구역은 기본 `1` 이며, 값이 `0` 인 옛 구역이 있다면 콘솔에서 확인하세요.
</Warning>

### Trigger — 개인화 액션

| 필드          | 타입                     | 설명                                              |
| ----------- | ---------------------- | ----------------------------------------------- |
| `triggerId` | `String`               | 트리거 식별자                                         |
| `type`      | `String`               | `signage · coupon · tracking · merch · generic` |
| `payload`   | `Map<String, String>?` | 타입별 부가 정보                                       |

### MockPositioningProvider — 테스트용

```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)
}
```

`co.onecheck.ones1ght.android.positioning` 패키지에 있으며, `session.begin(provider)` 로 주입해 UWB 없이 SDK 파이프라인을
확인합니다. `ZoneEventStatus` 는 `ENTER` · `DWELL` · `EXIT` 입니다.

## 스레딩

* Kotlin `suspend` API 는 어느 코루틴에서 불러도 됩니다. SDK 가 내부에서 스레드를 맞춥니다.
* `identify`·`empty`·`pause`·`resume` 은 **메인 스레드**에서 호출합니다.
* 콜백(`onPosition` 등)과 Java `Callback` 결과는 메인 스레드에서 호출됩니다 — UI 갱신을 바로 해도 안전합니다.

## 알아둘 제약

<Warning>
  Android 의 UWB 측위는 **포그라운드 전용**입니다. 앱이 백그라운드로 가면 측위가 멈추고 남은 좌표를 전송한 뒤,
  돌아오면 다시 시작합니다. 플랫폼 제약이라 SDK 가 우회할 수 없습니다.
</Warning>

<Warning>
  좌표 버퍼는 메모리에 있습니다. 앱이 강제 종료되면 아직 올리지 못한 좌표는 사라집니다(`E5006`).
</Warning>
