# StreamZet Android SDK 0.2.0-rc.2

Documentation revision: 2026-09-22

https://streamzet.com/docs

## Android 시작하기

테스트 앱을 설치하거나 같은 AAR을 ComponentActivity에 연동합니다. UI와 권한 안내는 영어·한국어·일본어를 지원합니다.

### 테스트 앱

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| jci-sample-debug.apk | com.streamzet.sample.jci | 상품·수량 입력과 지원 시작/종료를 제공하는 업무 앱 예제입니다. 실제 재고는 변경하지 않습니다. |
| streamzet-support-debug.apk | com.streamzet.support | 같은 공개 SDK를 쓰는 단독 지원 UI입니다. 평가용 debug 서명이며 운영 배포용이 아닙니다. |

```sh
adb install -r jci-sample-debug.apk
# Or install the support app:
adb install -r streamzet-support-debug.apk
```

앱에서 지원 시작을 누르면 등록·앱 안내·선택적 접근성·알림·Android 화면공유 동의가 순서대로 진행됩니다. Devices → 기기 추가에서 표시된 기기 코드를 연결하세요. 접근성 없이 화면만 공유할 수 있습니다. 기기는 동의한 지원이 실행 중일 때만 온라인입니다.

- [스크린샷으로 따라 하는 앱 사용법](https://streamzet.com/docs/android/test-apps)

### AAR과 호스트 설정

전달된 로컬 Maven 저장소에서 com.streamzet:remote-support-android:0.2.0-rc.2를 사용해야 전이 의존성이 해결됩니다. AAR만 복사하면 부족합니다. JDK 17, compileSdk 35, minSdk 26으로 빌드합니다. 병합된 SDK 서비스 선언을 유지하고 샘플 manifest처럼 기기 ID 백업을 금지하세요.

```kotlin
// settings.gradle.kts: add to dependencyResolutionManagement.repositories
maven { url = uri("vendor/streamzet-maven") }
google()
mavenCentral()

// app/build.gradle.kts
implementation("com.streamzet:remote-support-android:0.2.0-rc.2")
```

```kotlin
private lateinit var launcher: SupportLauncher

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val support = StreamzetSupport.getInstance()
        ?: StreamzetSupport.initialize(applicationContext, SupportConfig(
            supabaseUrl = "https://YOUR_BACKEND",
            publishableKey = "YOUR_PUBLIC_ANON_KEY",
            signalingUrl = "wss://YOUR_SIGNALING",
            deviceName = "Store handheld",
        ))
    launcher = SupportLauncher.bind(this, support) { ui ->
        // Render ui.message, ui.deviceCode, ui.canStart and ui.canStop.
    }
    // From user actions: launcher.start() / launcher.stop()
}
```

회전 후에도 Activity마다 STARTED 이전에 한 번 bind하고 프로세스 인스턴스는 재사용하세요. onDestroy에서 support.close를 호출하지 마세요. Activity 파괴는 UI만 분리하며 승인된 세션은 종료하지 않습니다. 새 세션마다 Android 동의가 필요하고 프로세스 종료 시 이전 동의는 무효입니다.

- [전체 코드와 Manifest로 앱 연동](https://streamzet.com/docs/android/integration)

### 테스트 앱 빌드

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| supabase.url | String · HTTPS origin | 빌드 설정. 템플릿에 기본 프로젝트 URL이 제공됩니다. |
| supabase.publicKey | String · required | 공개/anon 키만 사용. 누락 시 앱의 지원 시작을 비활성화합니다. |
| signaling.url | String · WSS origin | 빌드 설정. 템플릿에 기본 시그널링 주소가 제공됩니다. |
| sdk.registrationKey | String · empty | 선택적 비공개 테스트 빌드용 등록 키. SupportConfig.registrationKey로 전달됩니다. APK의 값은 추출 가능하므로 공용 배포 APK에서는 비워두세요. |

```sh
cp sdk-test.example.properties sdk-test.properties
# Configure the local file, then:
./gradlew :sample:assembleDebug :app:assembleDebug
```

### 동작과 제한

승인된 세션당 인증된 운영자 1명입니다. 무인 화면공유·셸/Fleet·파일전송·앱 설치 API는 없습니다. 보안/비밀번호 필드는 제외하며 커스텀 위젯과 OEM 접근성 동작은 실기 검증이 필요합니다. Enter/검색은 API 30 이상에서 편집기 액션을 사용하고 API 26~29에서는 앱의 제출 버튼을 누릅니다. UI는 Android 앱/시스템 언어를 따르고 enum 코드는 바뀌지 않습니다.

## 앱에 연동하기

받은 Maven 패키지부터 컴파일 가능한 Activity, 권한, 수명주기와 릴리스 확인까지.

### 1. 준비물과 호환 범위

SDK 0.2.0-rc.2, Android 8/API 26 이상 선언, compileSdk 35, JDK/JVM target 17 기준입니다. 이 문서의 예제는 AGP 8.7.3·Kotlin 2.0.21·Gradle 8.14.3으로 빌드했습니다. RC는 통합 검토용이며 모든 제조사 기기 인증을 뜻하지 않습니다.

관리자에게 HTTPS backend origin, 클라이언트 공개 anon JWT, WSS signaling origin, 테스트 대시보드 계정과 페어링 절차를 받습니다. SDK에는 서버 값이나 계정이 들어 있지 않습니다. registrationKey는 선택적 테넌트 등록용 비밀값이므로 일반 배포 APK에 넣지 않습니다.

### 2. Maven 의존성 추가

ZIP의 maven 디렉터리를 호스트 프로젝트의 vendor/streamzet-maven에 복사합니다. 아래 설정은 호스트 프로젝트 루트 기준입니다. POM과 Gradle module 파일을 함께 보존하세요. 타사 의존성 다운로드에는 Google Maven과 Maven Central 접근이 필요합니다.

```kotlin
// settings.gradle.kts — inside dependencyResolutionManagement
repositories {
    google()
    mavenCentral()
    maven { url = uri("vendor/streamzet-maven") }
}

// app/build.gradle.kts
android {
    compileSdk = 35
    defaultConfig { minSdk = 26 }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions { jvmTarget = "17" }
}
dependencies {
    implementation("com.streamzet:remote-support-android:0.2.0-rc.2")
}
```

> AAR 파일 하나만 복사하면 전이 의존성이 연결되지 않습니다. 기본 전달물인 Maven 폴더를 사용하세요. 기존 앱이 다른 org.webrtc 배포본을 쓰면 Java 클래스와 native library 중복을 먼저 해결하고 R8 release 실행까지 확인해야 합니다.

### 3. 완전한 최소 Activity

아래 파일에는 필요한 import와 화면 코드가 모두 들어 있습니다. 세 가지 YOUR_* 값을 제공받은 공개 연결 정보로 바꾸세요. 기존 업무 앱에는 SDK 초기화, bind, 상태 표시, start/stop 호출만 옮기면 됩니다. 테스트 프로젝트의 package·namespace는 com.example.support입니다.

```kotlin
package com.example.support

import android.os.Bundle
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.ComponentActivity
import com.streamzet.sdk.StreamzetSupport
import com.streamzet.sdk.SupportConfig
import com.streamzet.sdk.SupportLauncher

/** Replace the three endpoint placeholders with the public settings supplied to your project. */
class MinimalSupportActivity : ComponentActivity() {
    private lateinit var launcher: SupportLauncher

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val status = TextView(this)
        val code = TextView(this)
        val start = Button(this).apply { text = "Start support" }
        val stop = Button(this).apply { text = "Stop support" }
        setContentView(LinearLayout(this).apply {
            orientation = LinearLayout.VERTICAL
            setPadding(32, 96, 32, 32)
            addView(status)
            addView(code)
            addView(start)
            addView(stop)
        })
        val support = StreamzetSupport.getInstance() ?: StreamzetSupport.initialize(
            applicationContext,
            SupportConfig(
                supabaseUrl = "https://YOUR_BACKEND",
                publishableKey = "YOUR_PUBLIC_ANON_KEY",
                signalingUrl = "wss://YOUR_SIGNALING",
                deviceName = "Integration test",
            ),
        )
        launcher = SupportLauncher.bind(this, support) { ui ->
            status.text = ui.message
            code.text = ui.deviceCode.orEmpty()
            start.isEnabled = ui.canStart
            stop.isEnabled = ui.canStop
        }
        start.setOnClickListener { launcher.start() }
        stop.setOnClickListener { launcher.stop() }
    }
}
```

```xml
<!-- app/src/main/AndroidManifest.xml: standalone example Activity -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:theme="@android:style/Theme.Material.Light.NoActionBar">
        <activity android:name=".MinimalSupportActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
```

### 4. 권한과 앱 식별

AAR manifest가 INTERNET, foreground service/mediaProjection, POST_NOTIFICATIONS와 캡처·접근성 서비스를 병합합니다. 동일 서비스를 다시 선언하지 말고 Android Studio의 Merged Manifest에서 확인하세요. 사용자에게 매 세션 화면공유 승인을 받고, 원격 입력에는 해당 앱의 접근성 허용이 추가로 필요합니다.

접근성 설정에서 다른 앱과 구별되도록 호스트 리소스에 아래 이름을 정의하고 앱 지원 언어별로 번역하세요. Android의 제한된 설정 안내가 나오면 해당 기기의 공식 관리 정책에 따라 사용자 승인 경로를 사용합니다.

```xml
<!-- app/src/main/res/values/strings.xml -->
<resources>
    <string name="streamzet_accessibility_label">My Store remote support</string>
</resources>
```

### 5. 수명주기와 오류 처리

1. Activity의 onCreate에서 bind를 한 번 호출합니다. 시작 버튼 안이나 onStart에서는 bind하지 않습니다. 회전으로 재생성되면 동일 SDK 인스턴스를 다시 bind합니다.
2. start/stop은 메인 스레드의 사용자 동작에서 호출합니다. ACCEPTED는 요청 접수이며 CONNECTED 상태가 실제 연결입니다. canStart/canStop으로 버튼을 제어하고 error·stopReason·preparationError enum으로 처리합니다.
3. 업무 화면의 onStop/onDestroy에서 SDK stop/close를 호출하지 않습니다. 화면 이동은 지원 종료가 아닙니다. 명시적인 종료 버튼이 캡처와 입력을 종료합니다.
4. 사용자에게 ui.message를 표시하되 번역 문자열을 비교해 로직을 분기하지 않습니다. 기존 ViewBinding·Compose·Fragment는 호스트 Activity에서 만든 launcher를 전달받아 연결할 수 있습니다.

### 6. 호스트 앱 출시 전 확인

debug 빌드 성공만으로 끝내지 말고 실제 서명된 minify release 앱에서 공유·입력·종료·재연결을 확인합니다. 동일 applicationId와 서명, 앱 데이터를 유지한 업데이트는 등록을 유지합니다. 앱 데이터 삭제·제거·backend 변경은 새 등록이 필요할 수 있습니다. SDK는 호스트 APK를 자동 업데이트하지 않습니다.

- [테스트 앱 사용 가이드와 인수 체크리스트](https://streamzet.com/docs/android/test-apps)

## Android API 명세

공개 설정과 메서드 계약 전체입니다. Kotlin 식별자는 문서 언어와 무관하게 동일합니다.

### SupportConfig

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| supabaseUrl | String · required | HTTPS origin. 포트·끝 / 허용. 사용자 정보·하위 경로·쿼리·fragment 금지. |
| publishableKey | String · required | 공백이 아닌 public/anon 키. 운영자 또는 service-role 자격 증명이 아닙니다. |
| signalingUrl | String · required | supabaseUrl과 같은 제한을 따르는 WSS origin. |
| deviceName | String · required | 공백 불가, Kotlin UTF-16 코드 유닛 128개 이하(String.length). 바이트 기준이 아닙니다. |
| sessionTimeoutSeconds | Int · 0 | 초 단위. 0은 시간 제한 없음, 그 외 60~14400 포함. 사용자·운영자·OS 종료는 계속 적용됩니다. |
| connectionTimeoutSeconds | Int · 120 | 초 단위, 15~300 포함. 연결 협상 재시도 watchdog이며 공유 동의를 종료하지 않습니다. |
| framesPerSecond | Int · 30 | 요청 캡처 속도, 1~30 포함. 실제 FPS는 부하·통신에 따라 달라집니다. |
| maxVideoDimension | Int · 1600 | 인코딩 영상의 긴 변 픽셀 수. 0은 원본 해상도, 그 외 640~3840 포함. 비율 유지. |
| registrationKey | String? · null | 선택적 테넌트 등록 키. sz_live_ 접두사, 길이 128 이하. 등록에만 사용. null이면 기기 코드 연결. |

설정은 불변입니다. 잘못된 필드는 생성 시 예외를 발생시킵니다(IllegalArgumentException, URI 구문 오류는 URISyntaxException 가능). 생성자는 통신하지 않으며 toString은 키를 숨깁니다.

### SupportLauncher · 권장 흐름

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| bind(activity, support, listener) | → SupportLauncher | activity: STARTED 이전 ComponentActivity. support: 초기화된 프로세스 인스턴스. listener: 메인 스레드 SupportUiListener. Activity당 한 번, 회전 시 support 재사용. 잘못된 생명주기·중복 bind는 IllegalStateException. |
| state | SupportUiState | 렌더링용 스냅샷. 모든 필드는 상태 명세에 설명합니다. |
| start() | → SupportStartResult | 메인 스레드, RESUMED Activity의 사용자 동작에서 호출. ACCEPTED는 준비 접수이며 등록·권한·접속 성공을 보장하지 않습니다. |
| stop() | → Unit | 메인 스레드. 준비 취소 또는 지원 종료. 반복 호출 가능. Activity 파괴만으로는 코디네이터만 분리합니다. |

### StreamzetSupport · 수명주기와 수동 흐름

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| initialize(context, config) | → StreamzetSupport | context: Android Context, applicationContext로 보관. config: SupportConfig. 메인 스레드·프로세스당 한 번. 중복 초기화는 IllegalStateException. |
| getInstance() | → StreamzetSupport? | 기존 인스턴스. 초기화 전·close 후 null. 자동 초기화하지 않습니다. |
| register() | suspend → Result<DeviceRegistration> | 모든 dispatcher. 동시 호출은 요청 공유. 지원 중에는 실패 Result. 통신·인증 오류는 정제된 실패와 ERROR/REGISTRATION_FAILED. 닫힌 SDK는 예외, 코루틴 취소는 전파. 등록만으로 화면공유·온라인 보고하지 않습니다. |
| state | StateFlow<SupportState> | 읽기 전용 최신 상태. 호스트 생명주기에 맞춰 수집. |
| isControlAvailable | Boolean | 현재 접근성 입력 서비스 사용 가능 여부. true여도 운영자 연결이나 모든 위젯 조작을 보장하지 않습니다. |
| createScreenCaptureIntent() | → Intent | 수동 흐름 전용. 등록 후 표시된 화면의 사용자 요청·메인 스레드·지원 비활성 상태에서 호출. AWAITING_CONSENT로 전환. Activity Result API로 실행. 미등록·활성·닫힘 상태는 IllegalStateException. |
| onScreenCaptureResult(activity, resultCode, data) | → Boolean | activity: 살아 있는 Activity. resultCode: 시스템 Int. data: Intent?. 대기 결과 1회 소비. 거부·null·오래되거나 중복된 결과·서비스 시작 실패는 false. true는 서비스 시작 접수이며 연결 상태가 아닙니다. 승인 data는 저장·재사용 금지. |
| accessibilitySettingsIntent() | → Intent | Android 접근성 설정 Intent. 호스트 UI에서 실행. 권한을 자동 부여하지 않습니다. |
| addListener(listener) | → Unit | SupportListener에 현재·향후 SupportState를 메인 스레드로 전달. 같은 리스너 중복 제거, 콜백 예외 격리. |
| removeListener(listener) | → Unit | 메인 스레드. 화면 파괴 시 같은 리스너 인스턴스를 제거. 미등록 리스너 제거는 무해. |
| stop() | → Unit | 메인 스레드. 등록·승인 대기 중에도 반복 호출 가능. 캡처·입력·시그널링 종료. 닫힌 SDK에는 아무 작업 없음. |
| close() | → Unit | 메인 스레드. 지원·리스너·통신 자원 해제 후 singleton 초기화. 반복 가능. Activity 회전이 아니라 호스트가 SDK를 더 이상 쓰지 않을 때 호출. |

비-suspend 생명주기 메서드는 메인 스레드 API이며 다른 스레드 호출은 IllegalStateException을 발생시킬 수 있습니다. SupportListener.onStateChanged(state: SupportState), SupportUiListener.onStateChanged(state: SupportUiState)는 Unit 반환. RemoteInputService는 Android 바인딩용이며 직접 생성하거나 프레임워크 생명주기를 호출하지 마세요.

## Android 상태와 오류

공개 상태 필드와 enum 값 전체입니다. 코드는 분기에, 번역된 메시지는 표시에 사용하세요.

### DeviceRegistration

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| deviceId | String · required | 서버 기기 UUID. 표시·로그용 식별자이며 비밀 키가 아닙니다. |
| deviceCode | String · required | 대시보드 연결 코드. 등록 후 제공. 길이로 형식·유효성을 추정하지 마세요. |

### SupportState

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| phase | SupportPhase · IDLE | 생명주기 단계. 아래 enum 참조. |
| device | DeviceRegistration? · null | 등록 스냅샷. 종료·오류 후에도 남을 수 있으며 온라인 상태가 아닙니다. |
| controlAvailable | Boolean · false | 상태 갱신 시점의 접근성 서비스 사용 가능 여부. |
| stopReason | StopReason? · null | 최근 종료 전환 사유. 해당하지 않으면 null. |
| error | SupportError? · null | 정제된 오류 분류. 해당하지 않으면 null. 원시 HTTP·토큰·SDP 미포함. |
| isActive | Boolean · computed | AWAITING_CONSENT, STARTING, WAITING_FOR_OPERATOR, CONNECTING, CONNECTED, RECONNECTING, STOPPING이면 true. REGISTERING은 false. |

### SupportUiState

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| support | SupportState · required | 기본 상태. launcher가 조작 가능 여부를 갱신합니다. |
| message | String · required | en/ko/ja 상태 또는 준비 오류 메시지. 이 문자열로 로직을 분기하지 마세요. |
| isPreparing | Boolean · required | 등록·안내·권한 준비 진행 여부. |
| preparationError | SupportPreparationError? · required | 평상시 null. 설정·동의 UI 실행 실패. 이후 start로 재시도 가능. |
| canStart | Boolean · required | 준비·활성 세션·등록이 없을 때 true. start()는 Activity RESUMED도 추가 확인. |
| canStop | Boolean · required | 준비·등록·활성 지원 단계에서 종료 가능. |
| deviceCode | String? · computed | support.device?.deviceCode. 등록 전 null. |
| controlAvailable | Boolean · computed | support.controlAvailable의 별칭. |

### SupportPhase

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| IDLE | enum | 초기화됨, 활성 요청 없음. |
| REGISTERING | enum | 설치 인스턴스 등록·인증 중. |
| READY | enum | 등록 완료, 화면공유 동의 전. |
| AWAITING_CONSENT | enum | Android 캡처 동의 대기. |
| STARTING | enum | 포그라운드 캡처 서비스 시작 중. |
| WAITING_FOR_OPERATOR | enum | 동의된 공유에서 운영자 대기. |
| CONNECTING | enum | 피어 연결 협상 중. |
| CONNECTED | enum | 인증된 피어 연결됨. |
| RECONNECTING | enum | 동의를 유지하며 연결 복구 중. |
| STOPPING | enum | 명시적 종료 진행 중. |
| STOPPED | enum | 공유 종료, stopReason 확인. |
| ERROR | enum | 실패, error와 필요 시 stopReason 확인. |

### StopReason

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| USER | enum | 호스트·사용자가 지원 종료. |
| OPERATOR | enum | 원격 운영자가 지원 종료. |
| SYSTEM | enum | Android가 캡처 철회 또는 서비스 종료. |
| TIMEOUT | enum | 설정된 세션 시간이 만료됨. |
| PERMISSION_DENIED | enum | 캡처 승인 거부·무효 또는 Activity 사용 불가. |
| ERROR | enum | 오류로 지원·준비 종료. |

### SupportError

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| REGISTRATION_FAILED | enum | 등록·초기 인증 실패. 설정·통신 확인 후 재시도. |
| AUTHENTICATION_FAILED | enum | 세션·기기 인증 실패. 등록과 계정 권한 확인. |
| NETWORK_UNAVAILABLE | enum | 네트워크 실패 분류. 통신 확인. |
| CAPTURE_FAILED | enum | 화면 캡처 시작·유지 실패. 새 승인 요청. |
| CONNECTION_FAILED | enum | 피어 연결 실패 분류. 시그널링·TURN 확인. |
| SERVICE_START_FAILED | enum | Android가 포그라운드 서비스 시작 거부. 표시된 Activity에서 시작. |

### SupportStartResult

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| ACCEPTED | enum | 준비 접수, 연결 보장 아님. |
| ALREADY_ACTIVE | enum | 지원이 이미 활성화됨. |
| BUSY | enum | 준비·등록이 이미 진행 중. |
| HOST_NOT_RESUMED | enum | Activity가 RESUMED가 아니거나 종료 중·파괴됨. |

### SupportPreparationError

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| SETTINGS_UNAVAILABLE | enum | 접근성 설정 실행 실패. |
| CAPTURE_CONSENT_UNAVAILABLE | enum | Android 캡처 동의 실행 실패. |

## 테스트 앱 사용 가이드

업무앱 샘플과 독립 지원 앱을 설치하고, 사용자가 승인한 원격지원을 시작·검증·종료합니다.

### 1. 두 앱의 차이

| 앱 / 파일 | 용도 |
| --- | --- |
| JCI Store · Support sample / jci-sample-debug.apk | 기존 업무 화면에 SDK를 붙인 예제. 상품 코드·수량·조회 버튼으로 터치와 텍스트를 시험합니다. 실제 재고를 변경하지 않습니다. |
| Streamzet Support / streamzet-support-debug.apk | 지원 시작·종료에 집중한 독립 앱. 다른 앱으로 이동해 화면 공유 동작을 확인할 수 있습니다. |

두 앱은 applicationId와 등록 정보를 따로 보관하므로 기기 코드도 서로 다릅니다. 한 번에 하나의 앱에서 공유하고, 다른 앱을 시험하기 전에 현재 지원을 종료하세요.

![업무앱 샘플의 상품 조회 화면](https://streamzet.com/docs-assets/android/images/host-overview.png)

업무앱 샘플. 화면 문구는 Android 앱 언어를 따르며 웹 문서 언어와 독립적입니다.

> 스크린샷은 Android 16 에뮬레이터의 실제 앱 화면입니다. 공개 문서 촬영용 로컬 표시 코드는 DEMO0001이며 연결에 사용할 수 없습니다. 실제 시험에서는 본인 앱에 표시된 코드를 사용하세요.

![독립 지원 앱의 시작 화면](https://streamzet.com/docs-assets/android/images/support-overview.png)

Streamzet Support는 업무 입력란 없이 원격지원 상태와 시작·종료만 제공합니다.

### 2. 설치하고 처음 실행하기

1. 전달받은 ZIP의 SHA-256과 내부 SHA256SUMS를 확인합니다. Android 8 이상 테스트 기기, 인터넷, 운영자 대시보드 계정을 준비합니다.
2. 파일 앱 또는 adb로 두 APK를 설치합니다. 이 APK는 개발용 debug 서명이며 앱스토어 배포용 호스트 앱이 아닙니다. 기존 앱과 서명이 다르면 업데이트할 수 없으므로 관리자에게 맞는 빌드를 받습니다.
3. 연결 설정이 없다는 오류가 나오면 APK 제공자에게 해당 테스트 환경용 빌드를 요청합니다. 앱에는 URL·키를 입력하는 설정 화면이 없습니다. 소스 빌드 방법은 아래에 있습니다.

```sh
adb devices
adb install -r jci-sample-debug.apk
adb install -r streamzet-support-debug.apk
adb shell am start -n com.streamzet.sample.jci/com.streamzet.sample.MainActivity
# Standalone app:
adb shell am start -n com.streamzet.support/com.streamzet.app.MainActivity
```

### 3. 지원 시작과 권한 선택

1. 앱에서 원격지원 시작 / Start support를 누릅니다. 첫 실행은 등록 후 기기 코드를 표시합니다. 안내에서 운영자가 볼 수 있는 범위를 읽고 계속합니다.
2. 원격 터치·문자 입력이 필요하면 OPEN SETTINGS를 누릅니다. 업무앱은 JCI Store remote support, 독립 앱은 Streamzet Support remote input 항목을 켜고 Android 확인 창에서 허용한 뒤 앱으로 돌아옵니다.
3. 화면만 보여주려면 SHARE SCREEN ONLY를 선택합니다. 접근성을 켜지 않아도 화면을 공유할 수 있습니다. 알림 요청 거부는 화면 공유 거부와 다릅니다.
4. Android 화면공유 창에서 범위를 확인하고 Share screen을 선택합니다. Android 버전에 따라 단일 앱/전체 화면 선택과 문구가 다릅니다. Cancel이면 공유가 시작되지 않으며 다시 시도할 수 있습니다.

![지원 범위와 종료 방법 안내](https://streamzet.com/docs-assets/android/images/consent-introduction.png)

계속하기 전에 공유 범위를 읽고 동의합니다.

![원격 입력 또는 화면만 공유를 선택하는 안내](https://streamzet.com/docs-assets/android/images/control-choice.png)

접근성 허용은 선택 사항입니다. 화면만 공유 경로도 동일하게 시험하세요.

![Android 접근성 권한 확인](https://streamzet.com/docs-assets/android/images/accessibility-permission.png)

Android 16 테스트 화면. 실제 기기에서는 앱 이름과 OS 안내를 확인하고 사용자가 직접 승인합니다.

![Android 화면공유 승인 창](https://streamzet.com/docs-assets/android/images/screen-permission.png)

새 지원을 시작할 때마다 필요한 시스템 승인입니다. 다른 앱도 보려면 전체 화면 범위를 선택합니다.

### 4. 운영자 연결과 왕복 확인

1. 앱에 표시된 8자리 코드를 승인된 운영자에게 전달합니다. 코드를 공개 게시하거나 스크린샷·로그에 포함해 외부 배포하지 않습니다. 코드 표시만으로 공유가 켜진 것은 아닙니다.
2. 운영자는 대시보드의 Devices → Pair Device에서 해당 앱의 코드를 입력합니다. 테넌트 자동 등록을 사용했다면 관리자 안내에 따라 이미 배정된 기기를 선택합니다.
3. 앱의 운영자 대기 상태를 확인하고 대시보드에서 원격지원을 시작합니다. 영상이 움직이는지, 연결 상태와 원격 입력 가능 표시가 맞는지 확인합니다.
4. 업무앱의 상품 입력란을 원격으로 눌러 SDK-TEST-123을 입력하고 수량을 바꾼 뒤 Check product를 누릅니다. 독립 앱은 승인한 테스트 앱으로 이동해 터치·뒤로·홈·문자 입력을 확인합니다. 비밀번호·개인정보는 테스트에 사용하지 않습니다.
5. 화면만 공유 세션에서는 입력이 차단되어야 합니다. 업무 화면 회전·다른 화면 이동 후 영상이 계속되는지 확인합니다. 업무앱의 상품 조회 결과는 고정된 데모이며 바코드 장비·재고 시스템 검증을 대신하지 않습니다.

### 5. 종료·재시작·업데이트

앱의 Stop support, 대시보드의 End support, Android 공유 중지 중 하나로 종료합니다. 영상·입력이 중단되고 새 시작 버튼이 나타나는지 확인하세요. 새 세션은 다시 화면공유 승인이 필요합니다. 업무 화면을 닫거나 앱을 백그라운드로 보내는 것만으로 종료되지 않습니다.

동일 서명 업데이트와 앱 재실행 후 기기 코드가 유지되는지 확인합니다. 두 APK의 코드를 서로 바꾸어 사용하지 마세요. 앱 제거나 데이터 삭제를 테스트할 때는 새 등록이 생길 수 있으므로 기존 페어링과 기록의 처리 방식을 관리자와 맞춥니다.

### 문제 해결

| 증상 | 확인과 조치 |
| --- | --- |
| 설정 오류 / 시작 불가 | 공개 연결 값이 들어 있는 APK인지 확인하거나 sdk-test.properties를 채워 재빌드합니다. |
| 등록 실패 | HTTPS 접속, 공개 anon JWT, 프로젝트 주소, 선택적 등록 키의 유효성을 확인합니다. service_role 키를 넣지 않습니다. |
| 운영자 대기 | 승인한 앱과 연결한 기기 코드가 같은지, 계정 페어링·WSS 접속·TURN 허용이 맞는지 확인합니다. |
| 영상은 보이지만 입력 불가 | 보기 전용인지 확인하고 해당 앱의 접근성 항목을 켠 뒤 복귀합니다. 보안 입력란·보호 화면·제조사 제한은 별도입니다. |
| Enter 검색이 동작하지 않음 | API 30 이상에서 지원하는 IME 동작만 호출합니다. Android 8~10이나 커스텀 입력란에서는 화면의 검색 버튼을 탭합니다. |
| 서명 불일치 / 설치 실패 | 데이터 보존이 필요하면 앱을 삭제하지 말고 기존 서명으로 빌드된 APK를 받습니다. |

### 소스에서 테스트 앱 만들기

```sh
# Inside the delivered ZIP's source directory:
cp sdk-test.example.properties sdk-test.properties
# Set supabase.url, supabase.publicKey and signaling.url.
# Keep sdk.registrationKey empty for redistributable test APKs.
./gradlew :sample:assembleDebug :app:assembleDebug
# Both apps use the ZIP's Maven SDK by default.
# Outputs:
# sample/build/outputs/apk/debug/sample-debug.apk
# app/build/outputs/apk/debug/app-debug.apk
```

> 지원 요청에는 SDK/APK 버전, Android 버전·모델, 사용 앱, 발생 시각, 상태·오류 enum, 재현 단계와 비밀값을 제거한 로그를 첨부합니다. 계정 비밀번호·등록 키·기기 secret·TURN 토큰은 보내지 않습니다.
