# Android Studio / WSL ADB 연동 재발 방지 정책

작성일: 2026-07-03
상태: v1.3 Windows Android Studio emulator + WSL/Docker Flutter 기준

목적: Windows Android Studio emulator를 WSL 및 Docker 기반 Flutter CLI에서 사용할 때 발생한 ADB 연동 지연을 반복하지 않도록, 지연 원인과 동일 상황 발생 시 우선 처리 방식을 정책으로 고정한다.

관련 문서:

- `docs/troubleshooting/android-studio-wsl-adb-timetable-260703.md`
- `docs/scenario_android_emulator_device_integration_test_2026-07-03.md`
- `docs/dev_env_tdc114plus_setup_plan_2026-07-02.md`

## 1. 적용 범위

본 정책은 아래 상황에 적용한다.

- Windows Android Studio에서 Android emulator를 실행한다.
- Flutter CLI는 WSL 또는 Docker container 내부에서 실행한다.
- WSL/Docker Flutter에서 Windows emulator 또는 Android device를 인식해야 한다.
- `scripts/flutter-docker.sh devices` 또는 `scripts/integration_tests.sh`가 Android target을 필요로 한다.

아래 상황은 본 정책의 직접 적용 대상이 아니다.

- macOS 기반 iOS simulator
- WSL 내부 Android emulator 직접 설치
- 실기기만 사용하고 Windows ADB server를 거치지 않는 구성

## 2. 기본 원칙

- Windows Android Studio emulator를 사용할 때는 Windows `adb.exe` 기준으로 먼저 device 상태를 확인한다.
- WSL에 Android SDK/ADB가 없다고 판단되면 WSL 내부 emulator 설치로 바로 우회하지 않는다.
- `adb -a -P 5037 nodaemon server` 방식은 1차 시도만 허용한다.
- `10048` bind 실패가 1회라도 재현되면 즉시 Windows `portproxy` 방식으로 전환한다.
- Docker Flutter에는 `ADB_SERVER_SOCKET`을 명시적으로 전달한다.
- 2026-07-08 확인 결과, Windows `adb.exe devices`가 `device`인데 Docker local ADB 직접 연결이 `offline`으로 반복될 수 있으므로 기본 연결은 `ADB_SERVER_SOCKET=tcp:<WINDOWS_HOST_IP>:5037` 방식으로 한다.
- `TDC114_ADB_CONNECT_ADDRESS=<WINDOWS_HOST_IP>:<EMULATOR_PORT>` 기반 Docker local ADB server 방식은 `ADB_SERVER_SOCKET` 방식으로 integration test가 실제로 막힐 때만 보조 경로로 사용한다.
- Docker Flutter는 ADB key, Gradle, pub cache, Android SDK 하위 cache를 로컬 디렉터리에 유지한다.
- Android emulator에서 host API에 접근할 때는 `127.0.0.1`이 아니라 `10.0.2.2`를 우선 사용한다.
- Baron SSO backend, gateway, userfront, Ory runtime이 모두 살아 있는지 확인하기 전에는 Android 관련 테스트를 시작하지 않는다.
- integration test 실행 전에는 host 기준 `./scripts/api-smoke.sh`를 먼저 통과시킨다.
- 실행 결과와 장애 내용은 `docs/test-logs/YYYY-MM-test-execution-log.md`에 누적 기록한다.

## 3. 지연 원인

2026-07-03 작업에서 확인한 주요 지연 원인은 아래와 같다.

| 원인 | 영향 | 다음 대응 |
| --- | --- | --- |
| Windows ADB server가 `127.0.0.1:5037`에 먼저 바인딩됨 | WSL/Docker에서 직접 접근 불가 | `portproxy`로 `0.0.0.0:5037 -> 127.0.0.1:5037` 노출 |
| Android Studio, Device Manager, emulator, ADB client가 ADB server를 자동 재기동 | 기존 PID 종료 후에도 `adb -a` 재시도 실패 반복 | `adb -a` 반복 시도 금지 |
| `adb -a -P 5037 nodaemon server`가 `10048` 오류로 실패 | 외부 바인딩 방식 지연 | 1회 실패 후 `portproxy` 전환 |
| WSL 내부에 `adb`, `java`, `sdkmanager`, `emulator`가 없음 | WSL 단독 Android 환경 전환 불가 | Windows Android Studio emulator 사용 유지 |
| Docker container와 Windows ADB server의 localhost 의미가 다름 | integration test VM service dynamic port 연결 실패 가능 | 필요 시 당일 emulator adbd port를 노출 후 Docker local ADB server 방식 검증 |
| Docker local ADB server가 새 ADB key를 생성 | emulator가 `unauthorized` 상태로 표시 | Windows 승인 ADB key를 git ignored `.android-adb/`에 재사용 |
| Docker Flutter container가 매번 새로 생성됨 | NDK/CMake/Gradle/pub cache 재다운로드로 반복 지연 | `.docker-cache/flutter` 아래 cache volume 유지 |
| `scripts/integration_tests.sh` 출력이 종료 후 표시되는 구조였음 | Android 첫 빌드 진행 상태 확인 어려움 | 실시간 출력 방식 유지 |

## 4. 동일 상황 발생 시 처리 순서

### 4.1 Windows emulator 준비

1. Windows Android Studio를 실행한다.
2. Device Manager에서 emulator를 시작한다.
3. Windows PowerShell에서 `adb.exe devices`를 확인한다.

```powershell
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" devices
```

완료 기준:

- `emulator-5554 device` 또는 동일한 Android target이 `device` 상태로 표시된다.

### 4.2 `adb -a` 1차 시도

필요 시 아래 방식을 1회만 시도한다.

```powershell
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" -a -P 5037 nodaemon server
```

아래 결과가 나오면 더 반복하지 않는다.

- `10048`
- `0.0.0.0:5037` bind 실패
- 기존 ADB PID 종료 후에도 동일 실패 반복

### 4.3 `portproxy` 전환

관리자 PowerShell에서 아래 명령을 적용한다.

```powershell
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=5037 connectaddress=127.0.0.1 connectport=5037
netsh advfirewall firewall add rule name="ADB 5037 for WSL" dir=in action=allow protocol=TCP localport=5037
netsh interface portproxy show v4tov4
```

WSL에서 Windows host IP를 확인한다.

```bash
awk '/nameserver/ {print $2; exit}' /etc/resolv.conf
```

이번 환경의 예시는 아래와 같다.

```bash
172.21.128.1
```

### 4.4 WSL/Docker 연결 확인

WSL에서 TCP 연결을 먼저 확인한다.

```bash
nc -vz <WINDOWS_HOST_IP> 5037
```

Docker Flutter에서 device 인식을 확인한다.

```bash
ADB_SERVER_SOCKET=tcp:<WINDOWS_HOST_IP>:5037 ./scripts/flutter-docker.sh devices
```

완료 기준:

- Android emulator가 Flutter devices 목록에 표시된다.
- Linux desktop만 보이면 Android target 준비가 완료되지 않은 상태로 판단한다.

### 4.4-A Flutter/Docker 표준 연결 방식

기본 device 인식, 앱 실행, APK 설치 확인은 아래 방식을 표준으로 사용한다.

```bash
ADB_SERVER_SOCKET=tcp:<WINDOWS_HOST_IP>:5037 ./scripts/flutter-docker.sh devices
```

정책 기준:

- `5037 remote Windows ADB server` 방식은 현재 Windows emulator 연동의 기본 경로다.
- Windows에서 이미 `device`로 승인된 ADB 세션을 Docker Flutter가 공유한다.
- Docker local ADB가 직접 emulator port에 붙었을 때 `offline`이 반복되면 더 반복하지 않는다.
- 직접 emulator port 연결은 아래 보조 경로로만 둔다.

보조 경로:

```bash
TDC114_ADB_CONNECT_ADDRESS=<WINDOWS_HOST_IP>:<EMULATOR_PORT> ./scripts/flutter-docker.sh devices
```

### 4.5 Android emulator API 주소

Android emulator에서 host machine API를 호출할 때는 아래 주소를 사용한다.

```bash
TDC114_API_BASE=http://10.0.2.2:5000
```

emulator용 local env 파일 예시는 아래와 같다.

```text
scripts/.env.android-emulator.local
```

민감정보가 포함될 수 있으므로 해당 파일은 git tracked 파일로 추가하지 않는다.

## 5. Integration test 추가 분기

`ADB_SERVER_SOCKET=tcp:<WINDOWS_HOST_IP>:5037` 방식은 device 목록 확인과 APK 설치까지는 가능할 수 있다. 다만 Flutter integration test loading 단계에서 VM service dynamic port 연결이 실패할 수 있다.

대표 증상:

```text
WebSocketChannelException
127.0.0.1:<dynamic port> connection refused
```

이 경우 원인은 remote Windows ADB server가 만든 port forward의 `127.0.0.1`이 Docker container 내부 localhost와 일치하지 않는 구조일 가능성이 높다.

동일 증상이 실제로 발생한 경우에만 아래 순서로 보조 경로 전환을 검토한다.

1. Windows 관리자 PowerShell에서 emulator adbd port를 노출한다.

```powershell
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=<EMULATOR_PORT> connectaddress=127.0.0.1 connectport=<EMULATOR_PORT>
netsh advfirewall firewall add rule name="ADB emulator <EMULATOR_PORT> for WSL" dir=in action=allow protocol=TCP localport=<EMULATOR_PORT>
netsh interface portproxy show v4tov4
```

2. WSL에서 `<EMULATOR_PORT>` 연결을 확인한다.

```bash
nc -vz <WINDOWS_HOST_IP> <EMULATOR_PORT>
```

3. Docker container 내부 local ADB server가 emulator adbd에 직접 붙는지 확인한다.

```bash
docker run --rm ghcr.io/cirruslabs/flutter:stable sh -lc 'adb connect <WINDOWS_HOST_IP>:<EMULATOR_PORT> && adb devices'
```

4. 이 방식이 성공하면 `scripts/flutter-docker.sh` 또는 `scripts/integration_tests.sh`에 선택 환경변수를 추가한다.

예상 환경변수:

```bash
TDC114_ADB_CONNECT_ADDRESS=<WINDOWS_HOST_IP>:<EMULATOR_PORT>
```

5. Docker local ADB가 `unauthorized`이면 Windows에서 이미 승인된 ADB key를 재사용한다.

```bash
mkdir -p .android-adb
cp /mnt/c/Users/user/.android/adbkey .android-adb/adbkey.new
cp /mnt/c/Users/user/.android/adbkey.pub .android-adb/adbkey.pub.new
mv -f .android-adb/adbkey.new .android-adb/adbkey
mv -f .android-adb/adbkey.pub.new .android-adb/adbkey.pub
chmod 600 .android-adb/adbkey
```

6. 위 방식이 `device` 상태를 만들면 integration test는 아래 명령을 기본값으로 사용한다.

```bash
TDC114_SMOKE_ENV_FILE=scripts/.env.android-emulator.local \
TDC114_ADB_CONNECT_ADDRESS=<WINDOWS_HOST_IP>:<EMULATOR_PORT> \
./scripts/integration_tests.sh
```

## 5-A. API smoke 선행 원칙

Android integration test 전에 아래 명령을 먼저 실행한다.

```bash
./scripts/check-baron-api-env.sh
./scripts/api-smoke.sh
```

판정 기준:

- `./scripts/check-baron-api-env.sh`가 failure 0, warning 0 상태여야 한다.
- 최소한 아래 runtime이 running 또는 healthy 상태여야 한다.
  - `baron_backend`
  - `baron_gateway`
  - `baron_userfront`
  - `ory_kratos`
  - `ory_hydra`
  - `ory_keto`
  - `ory_oathkeeper`
  - `ory_postgres`
- phone-login, employee list, tenant list, orgchart가 모두 HTTP 200이어야 한다.
- unauthorized directory guard는 HTTP 401 또는 403이어야 한다.

실패 시 처리:

- `./scripts/check-baron-api-env.sh`에서 warning이 나오면 Android test보다 runtime 복구를 우선한다.
- `502 Bad Gateway`가 나오면 Baron/Ory runtime 중단 가능성을 먼저 의심한다.
- `./scripts/check-baron-api-env.sh`로 상태를 본다.
- `baron_backend`, `baron_userfront`, `ory_*` 컨테이너를 복구한 뒤 integration test를 재시도한다.
- `orgFront` 관련 API 확인이 필요한 경우에도 같은 원칙을 적용해 backend/gateway/userfront를 모두 확인한 뒤 진행한다.

## 5-B. Docker cache 유지 원칙

반복 실행 시 NDK/CMake/Gradle/pub cache 재설치를 막기 위해 `scripts/flutter-docker.sh`는 아래 로컬 cache 디렉터리를 유지한다.

- `.docker-cache/flutter/gradle`
- `.docker-cache/flutter/pub`
- `.docker-cache/flutter/android-sdk/licenses`
- `.docker-cache/flutter/android-sdk/ndk`
- `.docker-cache/flutter/android-sdk/cmake`

운영 원칙:

- 위 cache 디렉터리는 git tracked 파일로 추가하지 않는다.
- cache가 손상되지 않는 한 수동 삭제하지 않는다.
- Flutter Docker image를 바꾸더라도 우선 기존 cache와 호환되는지 확인한다.
- cache가 꼬여 비정상 빌드가 반복되면 해당 하위 디렉터리만 선별 삭제한다.

## 6. 금지 또는 제한 사항

- `adb -a -P 5037 nodaemon server` 실패 후 동일 명령을 여러 번 반복하지 않는다.
- Windows ADB PID를 계속 종료하면서 원인 확인 없이 시간을 쓰지 않는다.
- WSL에 `adb`, Java, Android SDK, `/dev/kvm` 조건이 없는데 WSL 내부 emulator 설치로 즉시 전환하지 않는다.
- 민감정보가 포함된 `.env.*.local` 파일을 git tracked 파일로 추가하지 않는다.
- `.android-adb/`, `.docker-cache/`를 git tracked 파일로 추가하지 않는다.
- Android emulator API base에 `http://127.0.0.1:5000`을 기본값으로 쓰지 않는다.
- `5037 remote ADB server` 방식에서 integration test VM service 실패가 재현됐는데 같은 방식만 반복하지 않는다.

## 7. 완료 체크리스트

다음 항목을 모두 만족하면 Android Studio / WSL ADB 연동 준비가 완료된 것으로 본다.

- Windows `adb.exe devices`에서 emulator가 `device` 상태다.
- `netsh interface portproxy show v4tov4`에 `5037` mapping이 존재한다.
- integration test에서 직접 연결 보조 경로가 필요하면 `netsh interface portproxy show v4tov4`에 당일 `<EMULATOR_PORT>` mapping도 존재한다.
- WSL에서 `<WINDOWS_HOST_IP>:5037` TCP 연결이 성공한다.
- `ADB_SERVER_SOCKET=tcp:<WINDOWS_HOST_IP>:5037 ./scripts/flutter-docker.sh devices`에서 Android target이 표시된다.
- 보조 경로 사용 시 `TDC114_ADB_CONNECT_ADDRESS=<WINDOWS_HOST_IP>:<EMULATOR_PORT> ./scripts/flutter-docker.sh devices`에서 Android target이 `device` 상태로 표시된다.
- emulator용 API base가 `http://10.0.2.2:5000`으로 설정되어 있다.
- `./scripts/check-baron-api-env.sh`가 warning 없이 통과한다.
- host 기준 `./scripts/api-smoke.sh`가 통과한다.
- integration test에서 VM service dynamic port 실패가 발생하면 당일 `<EMULATOR_PORT>` local ADB server 방식으로 분기한다.
- `.android-adb/`, `.docker-cache/`가 로컬에 유지되고 git tracked 상태가 아니다.
- 결과를 `docs/test-logs/YYYY-MM-test-execution-log.md`에 기록한다.
