
windowinsets.info를 만들고 있다. Android 기기별 상태 표시줄, 내비게이션 바, 제스처 영역, 화면 잘림과 폴더블 힌지 정보를 그림으로 보여 주어, 앱의 콘텐츠가 기기마다 어느 영역까지 안전하게 표시되는지 살펴볼 수 있다.

이 값은 기종과 화면, 회전 방향, 내비게이션 방식에 따라 달라진다. 그래서 작은 Android 앱인 InsetsProbe를 만들어 실기기와 Samsung Remote Test Lab(RTL)에서 값을 JSON으로 기록해 왔다. 기기가 많아질수록 측정 코드보다 기기를 조작하고 파일을 내 컴퓨터까지 옮기는 과정이 더 큰 일이 됐다.
처음에는 당시 내가 사용할 수 있던 OpenAI의 최상위 GPT-6 모델인 Astra에게 RTL 기기를 조작해 화면별 WindowInsets를 측정하고 JSON을 모으는 일을 맡기면 반복 작업이 빨리 끝날 줄 알았다. 그런데 검은 화면을 깨울 사이드 버튼을 못 누르고, 설정을 한 번 스크롤하면 메뉴를 다시 찾느라 헤맸다. JSON을 저장한 뒤에도 RTL File Browser의 다운로드 버튼이 반응하지 않아 파일을 내 컴퓨터로 가져오지 못할 때가 있었다.
기기 종류가 적을 때는 내가 RTL 기기에 Probe를 설치해 측정하고, 결과 JSON을 내려받아 Astra에게 넘기는 편이 빨랐다. 그런데 삼성 기기 약 120종을 등록한 뒤에야 가로 화면의 insets 확인 기능을 빠뜨렸다는 걸 알았다. 이를 채우려면 기기들을 다시 예약해 방향별 데이터를 처음부터 모아야 했다. Pixel 지원까지 더하면서 측정 대상도 늘었다. 수작업으로 파일을 모으는 방식은 엄두가 나지 않았다. 이 글은 그 병목을 겪고, Probe에서 GitHub PR까지 직접 업로드하는 흐름을 만든 기록이다.
RTL WebClient에 Probe APK를 설치해도 기기 화면이 검게 보일 때가 있었다. 화면을 켜려면 앱 안의 버튼이 아니라 WebClient가 그린 휴대폰 프레임의 사이드 버튼을 눌러야 했다.
브라우저 메뉴는 접근성 트리에서 이름을 찾을 수 있지만, 원격 Android 화면과 기기 프레임은 주로 픽셀로 보인다. Astra는 스크린샷에서 버튼처럼 보이는 돌출부를 찾아 좌표를 누르고, 다음 스크린샷에서 성공했는지 다시 확인해야 했다.

Fold8의 검은 화면. 눌러야 할 사이드 버튼은 앱 화면 밖, RTL이 그린 기기 프레임에 있다.
문제는 버튼을 알고도 클릭이 실제 기기에 전달됐는지 확신하기 어려웠다는 점이다. Fold8과 Flip8을 시험할 때도 Astra가 버튼을 눌러 보았지만 화면은 계속 검었다. 내가 화면을 깨우고 잠금을 풀어 준 뒤에야 다음 단계로 갈 수 있었다.
혹시 Claude의 최상위 모델인 Opus 5.5는 다를까 싶어 Fold7 RTL에서 APK 설치와 잠금 해제도 맡겨 봤다. 하지만 업로드와 클릭이 뜻대로 되지 않아 결국 내가 직접 두 단계를 끝냈다. 이번에는 모델을 바꿔도 원격 기기 조작의 어려움이 그대로였다. 그래도 응답은 Opus 쪽이 좀 더 빠릿빠릿하게 느껴져 마음에 들었다. Astra와 GPT도 이 부분은 분발했으면 싶다.

Opus 5.5로도 업로드와 클릭이 되지 않아 내가 설치와 잠금 해제를 마친 뒤 이어서 작업했다.
이번 측정에서는 Probe APK 설치, 사이드 버튼, 잠금 해제, Flip8 커버 위젯 등록과 실행에 내 손이 필요했다. 기기가 측정할 화면에 앱을 띄운 뒤에야 Astra가 Probe의 측정과 업로드를 이어갈 수 있었다.
Flip8 커버 화면은 특히 손이 많이 갔다. 내가 사이드 버튼을 눌러 화면을 켜 줘도, Astra가 다음 스크린샷을 확인하고 클릭할 좌표를 정하는 동안 화면이 다시 꺼졌다. 위젯을 등록한 뒤에는 커버 화면을 좌우로 스와이프해 해당 위젯을 찾아 실행해야 했는데, 이 조작도 Astra가 안정적으로 끝내지 못해 내가 직접 스와이프하고 위젯을 눌렀다.
WindowInsets는 제스처와 3버튼 내비게이션에서 값이 달라진다. 두 모드를 측정하려면 Android 설정에서 내비게이션 방식을 바꿔야 한다. 그런데 Astra가 스크린샷에서 찾은 메뉴 위치는 한 번 스크롤한 뒤 더 이상 맞지 않았다.
사람은 목록이 움직이는 동안 같은 항목을 눈으로 따라간다. Astra의 조작은 화면 A를 보고 판단 → 스크롤 → 화면 B를 다시 관찰하는 단계로 끊긴다. B를 확인하지 않고 A의 좌표로 누르면 엉뚱한 메뉴를 열게 된다.


Flip8 설정 메뉴를 스크롤하기 전과 후. 다음 클릭은 반드시 바뀐 화면에서 다시 찾아야 했다.
처음에는 앱에서 Settings.Secure.putInt(navigation_mode, ...)로 모드 변경도 시도했다. 호출 경로의 오류를 고쳐도 Samsung RTL 기기는 요청을 적용하지 않았다. 새 JSON은 계속 3버튼 모드였다.
Probe에 Display / navigation settings 버튼과 × 종료 버튼을 넣어 설정 앱으로 이동하기 쉽게 했다. 설정을 스크롤한 뒤에는 반드시 새 화면에서 항목을 다시 찾도록 했다. 모드 변경 여부는 요청 코드가 아니라 Android 설정과 새 캡처의 navigation.mode로 판단한다.
이번 시험 측정에서는 3버튼 모드의 Fold8·Flip8 데이터를 우선 모았다. 제스처로 바꾸지 않은 캡처를 제스처 데이터라고 부르지 않았다.
기존에는 RTL File Browser에서 Android/data/info.windowinsets.probe/files로 들어가 JSON을 내려받았다. 파일 행에 마우스를 올려야 다운로드 아이콘이 나타났다.

Flip8 RTL File Browser. JSON 파일 행에 마우스를 올려야 오른쪽에 다운로드 아이콘이 나타났다.
더 큰 문제는 다운로드 아이콘을 눌러도 아무 반응이 없는 경우였다. RTL WebClient의 문제인지 다른 원인인지는 확인하지 못했지만, 기기에서 측정을 마쳐 JSON을 저장해도 로컬 PC로 가져오지 못하면 그 결과를 다음 작업에 쓸 수 없었다. Flip3에서는 제스처 JSON을 끝내 받지 못해 다음 예약에서 다시 측정했다.
대안으로 WebClient Logs에서 InsetsProbe 로그를 저장해 Probe가 출력한 JSON을 복구할 수는 있었다. 다만 로그에서 캡처별 JSON을 추출하고 확인해야 하는 수작업이 남는다. 다운로드에 성공해도 파일 이름이 content, content (1)처럼 바뀌어, 한 파일씩 열어 모델·화면·모드·크기를 확인해야 했다.
InsetsProbe가 JSON을 /api/captures로 직접 보내게 했다. 이 주소는 사이트와 함께 배포한 Vercel Function의 API 엔드포인트다. Vercel이 요청을 받아 함수를 실행하므로 별도로 상시 운영할 서버는 필요하지 않았다. 함수는 업로드 키와 JSON 형식을 확인한 뒤 GitHub API로 capture-inbox 브랜치에 원본을 커밋한다. 업로드마다 PR을 만들지 않고 여러 캡처를 하나의 PR에 모은다. 병합 여부는 내가 결정하고, 병합할 때는 스쿼시 커밋으로 묶는다.

먼저 기존 S24+ JSON을 재전송해 PR #30에 도착하는지 확인했다. 여기까지는 업로드 경로를 시험한 것이었다.
이후 Fold8과 Flip8을 실제 RTL에서 예약해 APK를 설치했다. Probe 버튼을 눌러 만든 새 JSON이 같은 PR에 도착하는 것까지 확인했다. Fold8 커버와 Flip8 메인은 세로·가로 양방향 캡처가 올라왔다.

이제 RTL File Browser의 다운로드 버튼에 의존하지 않고, Probe가 측정한 JSON을 직접 전송한다.
Fold8의 과거 main JSON은 1248 × 1972px였다. 이 크기는 내측이 아니라 커버 화면과 맞는다. 예전 Measure All의 Cover/Main 선택은 실제 디스플레이를 바꾸지 않고 파일 라벨만 바꿨다.
실제 기기를 다시 측정하면서도 비슷한 실수를 했다. Fold8을 펼친 직후 Cover 라벨을 그대로 둔 채 한 번 측정해, 실제 2448 × 1848px 내측 화면이 cover-threeButton.json으로 올라갔다. API 성공은 전송 성공이지, 화면 분류가 맞다는 뜻이 아니었다.
폴더블 화면은 RTL의 접힘 제어로 실제 전환하고, Probe가 보고한 창 크기와 디스플레이를 확인한다. Fold8 커버는 1248 × 1972px, 내측은 2448 × 1848px로 구분했다. 잘못 표기된 원본은 고치지 않고 증거로 남기되, 유효한 커버 측정으로 사용하지 않는다.
Probe의 회전 스윕도 화면이 실제로 돌아갔을 때만 파일을 저장한다. Flip8 커버 위젯은 Display 1에서 948 × 1048px 세로 캡처를 업로드했지만, 가로 요청은 적용되지 않아 가로 파일을 만들지 않았다.


Flip8 커버 화면에서 직접 측정하고 업로드했다. 기기가 허용하지 않은 가로 값은 채우지 않았다.
가로 데이터를 다시 모을 때 가장 궁금했던 건 이거였다. 90°와 270°가 같은 값이라면 하나만 찍고 뒤집어 쓰면 된다. 다르다면 삼성 기기 약 120종에 더해 Pixel 기기까지 두 방향을 각각 측정해야 한다.
S23+ 바형, Fold8 커버, Flip8 메인을 같은 3버튼 조건으로 비교했다. 세 기기 모두 화면 크기는 같았지만, 상태 표시줄은 두 방향 모두 위쪽에 남았다. 내비게이션 바와 카메라 잘림 영역은 오른쪽에서 왼쪽으로 옮겨 갔다.
| 기기 | 90° 상태 표시줄 / 내비게이션 바 / 잘림 영역 | 270°에서 달라진 점 |
|---|---|---|
| S23+ | 위 84px / 오른쪽 135px / 왼쪽 74px | 상태 표시줄은 위 84px, 나머지는 좌우 반대 |
| Fold8 커버 | 위 79px / 오른쪽 126px / 왼쪽 104px | 상태 표시줄은 위 79px, 나머지는 좌우 반대 |
| Flip8 메인 | 위 90px / 오른쪽 144px / 왼쪽 108px | 상태 표시줄은 위 90px, 나머지는 좌우 반대 |
90° 캡처를 180° 돌리면 상태 표시줄까지 아래로 가므로 270° 실측값과 틀린다. 이 세 사례에서는 좌우 반전이 맞았지만, 모두 3버튼 모드였다. 제스처 모드와 다른 기종까지 같은 규칙이 적용된다고 단정할 수는 없다.
90°와 270°는 각각 실측한다고 결정했다. Probe의 회전 스윕은 한 번 누르면 두 방향을 연달아 시도한다. 별도 예약을 한 번 더 할 필요는 없다. 기기가 회전을 허용하지 않으면 그 방향은 저장하지 않고 미측정으로 남긴다.
이렇게 해야 사이트에서 보여 주는 값이 실제 기기에서 나온 측정인지 분명해진다. 좌우 반전으로 만든 그림이 필요하더라도 실측한 270° 데이터인 것처럼 표시하지 않는다.
한 대만 보면 내가 직접 버튼을 누르고 파일을 받는 편이 빨랐다. 하지만 다운로드 버튼이 반응하지 않으면 힘들게 측정한 JSON을 로컬 PC로 가져올 수 없었다. 로그에서 값을 복구하는 우회로는 있어도 매번 손으로 추출하고 검증해야 했다. 삼성 기기만 약 120종이고 Pixel까지 지원 범위를 넓히면서 이 방식은 감당하기 어려워졌다. 그래서 사람이 기기를 준비한 뒤 Probe가 가능한 회전을 측정하고 JSON을 Vercel API로 직접 전송하는 흐름으로 바꿨다. 이제 File Browser 다운로드의 속도나 성공 여부에 캡처 수집을 맡기지 않는다.
Fold8·Flip8 실기기에서 Probe → Vercel → GitHub PR까지 확인했다. 90°와 270°는 단순 회전으로 같은 값이 되지 않아 둘 다 실측하기로 했다. APK 설치와 사이드 버튼·잠금 해제, Flip8 커버 위젯 등록·실행에는 여전히 사람의 도움이 필요했다. 잘못 고른 화면 라벨도 업로드 전에 자동으로 막지 못했다. 원본 파일은 PR #30에서 스쿼시 머지했다. 이 머지는 증거 보관 단계이며, 사이트에 방향별 실측값을 연결하는 작업은 별개다.
Astra가 간단한 버튼에서 헤맨 이유도 조금은 알 것 같다. 웹 메뉴, 원격 화면, 기기 프레임은 조작 방식이 달랐다. 특히 스크린샷 한 장은 스크롤이나 화면 꺼짐 뒤의 상태를 보장하지 않는다. 다음 행동 전에 현재 화면을 다시 확인하는 절차가 필요했다.
그 뒤로 이 문제들을 하나씩 해결해, 지금은 측정 과정이 거의 자동화됐다.(Flip 기종 커버 화면 부분만 제외하면...)
이 시행착오를 거쳐 정리한 현재의 측정 루프와 하네스는 Measurement harness에 도식화해 두었다. RTL 기기 예약부터 Probe 측정, Vercel Function 업로드, 원본 검증까지 각 단계가 어떻게 이어지는지 볼 수 있다.
이 글을 쓴 뒤에도 한동안은 AI가 RTL 화면을 더 잘 누르게 만드는 방법을 찾았다. 방향이 틀렸다. 스크린샷을 보고 좌표를 누르는 조작은 모델을 바꿔도, 절차를 다듬어도 느리고 불안정했다. 그래서 AI가 UI를 직접 조작하는 일을 힘들어한다는 사실을 인정하고, UI를 거치지 않는 길을 찾았다.
RTL WebClient에는 Remote Debug Bridge(RDB)라는 메뉴가 있다. 연결하면 예약한 원격 기기가 내 컴퓨터의 adb에 잡힌다. 그다음부터는 화면을 볼 필요가 없다. Probe 설치, 내비게이션 모드 전환, 회전 고정, JSON 수집까지 스크립트 하나가 adb 명령으로 처리한다. 앞에서 문제 1~3으로 적었던 사이드 버튼, 설정 스크롤, 파일 다운로드가 한꺼번에 사라졌다. 브라우저에서 남은 일은 기기 예약, 연결, 반납뿐이다.
여기서 헤드리스는 기기 화면을 없앤다는 뜻이 아니다. 원격 기기의 화면과 SystemUI는 그대로 동작하므로 앱은 WindowInsets를 정상적으로 받는다. 그 화면을 스크린샷으로 보고 마우스로 누르는 대신 adb 명령으로 조작한다는 뜻이다.
기기 한 대의 캡처가 2분 안팎으로 끝난다. 하루 동안 30대 넘게 측정해, 밀려 있던 회전 측정 대기열을 비웠다.
물론 헤드리스라고 삽질이 없지는 않았다. 이전 사용자가 남긴 설정 때문에 값이 달라지는 기기가 있어서, 기존 캡처와 기준 방향 값이 같은지 먼저 비교하고 다르면 다른 기기로 다시 쟀다.
AI에게 사람처럼 화면을 누르게 하려고 애쓰기보다, AI가 잘하는 방식인 명령어와 텍스트로 일을 바꿔 주는 편이 훨씬 빨랐다.
기기 종류가 적을 때는 내가 RTL 기기에 Probe를 설치해 실행하고, 측정 결과 JSON을 내려받아 Astra에게 전달하는 편이 빨랐다. 하지만 삼성 기기 약 120종을 등록하고 Pixel 지원까지 더하자, 그 일을 계속할 수는 없었다. 그래서 내가 반복하던 파일 수집 단계를 없애려고 코드를 고쳤다. 요즘 개발 조직에서는 사람이 하던 일을 AI와 자동화로 줄이거나 없애는 일이 개발자의 ‘성과’로 평가되기도 한다.
친구와 얘기하다 보니 그 사실을 두고 고민이 깊어졌다. 내게는 반복 작업에서 벗어나는 일이지만, 누군가에게는 자신이 해 온 일이 필요 없어지는 순간일 수 있다. 자동화를 끝냈다는 성취감과 사람의 자리를 줄이는 기술을 만드는 건 아닐까 하는 불편함이 함께 남았다.
이 고민에 깔끔한 답은 없을 것 같다. 내가 겪은 번거로움을 다른 개발자도 덜 겪게 하는 제품을 만들고 싶었다. 파일을 옮기던 시간은 측정값이 맞는지 확인하고, 그 의미를 설명하는 데 쓰고 싶다. 그렇다고 자동화가 사람의 일에 미칠 영향에 대한 불편함까지 사라진 것은 아니다.
Flip5부터 커버 화면이 커지며 FlexWindow라는 이름이 붙었다. 이번에 측정한 Flip8에서는 APK를 설치했다고 커버 화면에서 앱이 바로 뜨지 않았다. 접은 채 RTL의 Applications에서 실행하면 숨겨진 메인 화면에 열리기도 했다. 결국 FlexWindow용 위젯을 만들어 커버 디스플레이에서 앱을 열고, 실제 화면 크기까지 확인해야 했다. 위젯을 만드는 것과 커버 화면에서 위젯을 등록하고 찾아 실행하는 것은 또 다른 일이었다.
180° 회전도 뜻밖이었다. 내가 확인한 Galaxy 바형 폰과 Fold·Flip의 일반 화면은 자동 회전만으로 세로 화면을 거꾸로 돌리지 않았다. Fold를 펼쳐도 이 점은 태블릿과 달랐다. 반면 Galaxy Tab은 거꾸로 든 세로 방향도 지원했다. 여기서 180°는 힌지 각도가 아니라 화면 방향이다. 이런 예외를 모르고 파일만 쌓았다면, 숫자는 늘어도 믿을 수 있는 데이터는 늘지 않았을 것이다.
다음 글에서는 Pixel 에뮬레이터에서 기기별 WindowInsets 측정을 어떻게 자동화했는지 다뤄 보겠다.