2026년 10월 31일 토요일

[안드로이드] 사진 액자 앱을 바이브코딩으로 직접 만들기 — 요구사항에서 홈 앱까지 (액자 시리즈 2편)

 

[안드로이드] 사진 액자 앱을 바이브코딩으로 직접 만들기 — 요구사항에서 홈 앱까지

서두

지난 글(오딘 플래싱 편)에서 웅진북클럽 태블릿을 순정 펌웨어로 되살렸다. 이제 이 기기를 거실 사진 액자로 만들 차례다. 구글포토 연동 액자 앱(Fotoo류)을 쓰려고 했지만, 2025년 4월부터 구글이 개인 앱의 사진 라이브러리 읽기 API를 차단해서 이 계열 앱들이 사실상 망가졌다. 그래서 결정했다: 구글 계정도, 서버도, 네트워크도 필요 없는 로컬 사진 액자 앱을 직접 만들자.

코딩은 전부 AI에게 부탁하는 바이브코딩으로 했다. 이 글의 프롬프트를 그대로 복사해서 쓸 수 있게 남긴다. 요구사항을 말하고 → 받은 코드를 넣고 → 실기기에서 돌려보고 → 부서지는 대로 고치는 반복이 전부다.

앱 설계 한눈에 보기

구성 요소역할
MainActivity사진 스캔 + 슬라이드쇼 + 제스처(스와이프·더블탭·탭)
BootReceiver부팅하면 액자 앱 자동 실행
NightReceiver밤 11시~아침 7시 화면 자동 어둡게
RefreshReceiverPC에서 사진 추가하면 즉시 재스캔
SettingsManager설정값(간격·밝기·밤 시간대) 저장/읽기

제약 조건을 먼저 정했다: Java만, 외부 라이브러리 없음, 안드로이드 8.0(minSdk 26) — 2016년 기기라 최신 기술은 전부 배제.

STEP 1. 최소 슬라이드쇼

프롬프트 1

안드로이드 사진 액자 앱을 만들어줘. 조건:
- Java만 사용, 외부 라이브러리 금지, minSdk 26
- 패키지명 com.handy.photoframe
- /sdcard/PhotoFrame 폴더의 이미지(jpg/png/webp)를 전부 읽어서
  풀스크린 ImageView로 30초마다 다음 사진을 보여준다
- 파일명 오름차순 정렬, 끝에 도달하면 처음으로 되돌아간다
- 화면 맨 위에 작은 디버그 텍스트(권한 여부, 사진 개수) 표시
- 사진 로딩 실패 시 화면이 검게 변하지 않게 마지막 사진 유지
- 액티비티는 세로·가로 회전에 대응(configChanges)
코드 전체를 파일별로 나눠서 줘.

여기서 나온 코드가 앱의 뼈대다. 스캔 → 정렬 → Handler로 30초마다 showNext() → ImageView에 비트맘. 비트맵은 크기 조절(샘플링)해서 메모리 폭발을 막도록 부탁하는 걸 잊지 말자.

STEP 2. 전원이 나가도 다시 살아나야 한다

액자는 24시간 돌아가니까 정전·재부팅 후에 사람 손이 안 대도 다시 켜져야 한다.

프롬프트 2

방금 만든 앱에 두 가지를 추가해줘:
1. BootReceiver: 부팅 완료 브로드캐스트를 받으면 MainActivity를 자동 실행
   (RECEIVE_BOOT_COMPLETED 권한, 매니페스트에 receiver 등록 포함)
2. MainActivity의 FLAG_KEEP_SCREEN_ON 추가 — 앱이 보이는 동안 화면이 절대 꺼지지 않게
주의사항: Android 8에서 부팅 브로드캐스트 제약 회피 방법도 설명해줘.

STEP 3. 밤에는 어둡게 — 여기서 첫 번째 전투

거실 액자는 밤에도 켜져 있으면 빛 공해다. 밤 11시~아침 7시에 화면을 완전히 끄자.

프롬프트 3

야간 모드를 추가해줘:
- SettingsManager: nightStart(23), nightEnd(7), intervalMs, dayBrightness를
  SharedPreferences로 저장
- 시간이 23~7시(자정 걸침 포함)면 화면 밝기를 0으로, 낮이면 120으로 복원
- WRITE_SETTINGS 권한 필요 — 권한 없으면 상단 텍스트를 탭했을 때
  권한 설정 화면으로 이동
- NightReceiver: 부팅 시와 시간대 변경 시 밝기를 다시 적용
단, 밤에는 사진을 아예 렌더링하지 말 것(완전 검은 화면).

실전에서 여기서 문제가 생겼다. 기존에 깔려 있던 다른 앱(웅진 잔존 앱)도 밝기를 계속 조작해서, 내 앱과 밝기 조작이 무한 루프로 싸우는 것. 해법: 싸우지 말고, 밝기를 바꿀 때 "내가 바꿨는지" 추적(nightApplied 플래그)해서 중복 조작을 없애는 것. 액자 앱끼리 밝기를 두고 싸우는 꼴은 흔한 함정이다.

STEP 4. 폴더 구조 — 재귀 스캔

사진을 주제별 폴더(가족/아이돌/학습)로 나누니 하위 폴더 스캔이 필요해졌다.

프롬프트 4

collect()가 /sdcard/PhotoFrame 아래를 재귀적으로 훑도록 바꿔줘.
이미지 확장자(jpg/jpeg/png/webp)만, 숨김폴더(.thumbnails) 제외.
단 목록이 자주 바뀌니, 슬라이드 20번마다 조용히 재스캔해서
개수가 바뀐 경우에만 목록을 갈아끼워줘 (표시 중 사진이 튀지 않게).

STEP 5. 홈 런처화 — 여기서 함정 두 번째

액자는 전원 켜면 바로 액자 화면이어야 한다. 앱을 기본 홈(런처)으로 만들자.

프롬프트 5

MainActivity를 홈 런처로 만들어줘. 매니페스트의 인텐트 필터에
ACTION_MAIN + CATEGORY_HOME + CATEGORY_DEFAULT를 추가해줘.

여기서 AI가 실수하기 딱 좋은 부분이 category를 action으로 잘못 쓰는 것. 실제로 내 앱은 <action android:name="android.intent.category.HOME"/>이라는, 문법上是 말이 안 되는 필터가 들어가 있었고 adb shell cmd package set-home-activity가 "cannot be home" 오류를 뱉었다. 홈 앱이 안 잡히면 인텐트 필터부터 의심하자.

STEP 6. 제스처 — 넘기고, 유튜브 열고

액자가 사람과 상호작용하기 시작하는 단계.

프롬프트 6

GestureDetector로 세 가지 제스처를 추가해줘:
1. 좌우 스와이프: 왼쪽으로 쓸면 다음 사진, 오른쪽이면 이전 사진
   (가로 이동이 세로보다 1.5배 크고 80px 이상일 때만, 자동 타이머는 리셋)
2. 사진 더블탭: org.schabi.newpipe(뉴파이프) 실행 — 설치돼 있을 때만
3. 한 번 탭: 설정 화면(SettingsActivity) 열기
제스처가 서로 충돌하지 않게 onDown은 true 반환, 터치는 ImageView에만 바인딩.

설계 포인트: 자동 슬라이드쇼는 "무작위"가 아니라 "날짜순(파일명순)"으로 정렬해뒀다. 스와이프로 넘기는 게 앨범처럼 느껴지려면 순서가 의미가 있어야 하기 때문이다.

STEP 7. 설정 화면

간격·밝기를 바꾸려고 PC를 꽂는 일이 있으면 안 된다.

프롬프트 7

SettingsActivity를 XML 없이 코드로 만들어줘:
- 사진 간격: 5초~300초 슬라이더 + 프리셋 버튼(5초/10초/30초/60초/5분)
- 낮 밝기 슬라이더(10~255), 밤 밝기 슬라이더(0~50)
- 밤 시작/끝 시간 ±버튼(0~23 순환)
- '사진 다시 읽기' 버튼(RefreshReceiver에 명시적 브로드캐스트)
- 닫기 버튼. 검은 배경 + 큰 글씨, FLAG_KEEP_SCREEN_ON.
변경값은 즉시 저장하고, MainActivity 슬라이드 루프는 매 틱
최신 설정값을 읽도록 (재시작 없이 적용).

함정 하나: PC에서 앱을 갱신 설치하면 기본 홈 역할이 삼성 런처로 튕겨 나간다. 재설치 후엔 adb shell cmd package set-home-activity <pkg>/.MainActivity를 다시 실행해야 한다.

STEP 8. 폴더 카테고리 + 꽉 찬 표시

프롬프트 8

1) 설정 화면에 표시 항목 선택 행을 추가: [전체][가족][아이돌] 버튼.
   PhotoFrame 루트의 하위 폴더(1_가족, 2_아이돌 등)를 동적으로 감지해서
   새 폴더가 생기면 버튼도 자동 추가. 선택은 저장 후 재부팅에도 유지.
2) 표시 모드 2종: 꽉 찬 채움(CENTER_CROP, 가장자리 절단)과 비율 맞춤(FIT_CENTER).
   기본은 꽉 찬 채움.
3) 몰입형 전체화면: 상태바·내비게이션바 숨김(IMMERSIVE_STICKY),
   사진이 화면 끝까지 차도록.

꽉 찬 채움만으로는 부족했다 — 시스템 상태바(시계)와 소프트 내비게이션바가 사진 위를 덮고 있어서, 몰입형 모드까지 켜야 진짜 테두리 없는 액자가 됐다.

실전에서 배운 것들

  1. 야간 렌더링 금지 — 밝기 0만으론 부족하다. 사진 자체를 안 그려야 진짜 검다.
  2. 밝기 조작 경합 — 다른 앱과 밝기를 두고 싸우지 말 것. 추적 플래그로 중복 조작 제거.
  3. 인텐트 필터 오타 — 홈이 안 잡히면 action/category 혼동부터 의심.
  4. Android 8 암시적 브로드캐스트 차단 — 매니페스트 수신자에게 am broadcast -a ...만 날리면 못 받는다. -n <pkg>/.Receiver로 명시적 컴포넌트를 지정하자 (PC 동기화 도구가 갱신 신호를 안 먹혀서 하루를 태웠다).
  5. 재설치마다 홈 역할 리셋 — 업데이트 후 set-home-activity 재실행 루틴화.
  6. 버전코드 습관 — versionCode를 올리지 않고 재설치하면 나중에 나 자신이 헷갈린다.

마무리 — 총 8번의 프롬프트로

최종 스펙: 사진 309장+ 웹 고화질, 10초 간격 슬라이드쇼, 좌우 스와이프 앨범 넘김, 더블탭 유튜브(뉴파이프), 폴더 카테고리 필터, 꽉 찬 전체화면, 밤 11시~7시 자동 어둡게, 부팅 자동 실행, PC 동기화 즉시 반영.

바이브코딩의 진짜 힘은 코드를 안 짜는 게 아니라, 요구사항을 정확히 말하는 능력이 곧 개발력이 된다는 데 있다. 실기 테스트에서 부서지는 대로 프롬프트를 고쳐 쓰는 반복 — 그게 이 앱의 개발 과정 전부였다.

다음 편 예고: PC에서 사진을 넣으면 액자에 즉시 반영되는 동기화 도구 만들기 (adb로 백엔드 없이).

웅진북클럽 태블릿(갤럭시탭 어드밴스드2)을 순정 펌웨어로 되살리기 — 오딘 플래싱 전 과정 (액자 시리즈 1편)

 

웅진북클럽 태블릿(갤럭시탭 어드밴스드2)을 순정 펌웨어로 되살리기 — 오딘 플래싱 전 과정

들어가며

웅진북클럽에서 배포한 갤럭시탭 어드밴스드2(SM-T583)는 삼성 제품이지만 웅진 전용 펌웨어가 얹힌 특판 기기다. 북클럽 앱이 시스템 영역에 박혀 있어 지울 수도 끌 수도 없고, 북클럽 서비스를 쓰지 않으면 사실상 벽돌이다.

이 글은 이 태블릿을 삼성 순정(KOO) 펌웨어로 교체해 일반 기기로 되살린 전 과정을 정리한 것이다. 결론부터 말하면 5분이면 끝나는데, 그 전 준비와 그 뒤의 함정이 진짜 내용이다.

경고: 플래싱은 데이터를 전부 지우고, 절차를 잘못하면 기기가 부팅하지 않을 수 있다. 반드시 백업 후 진행하고, 모든 책임은 본인에게 있다.

준비물

준비물설명
PC + USB 케이블데이터 전송이 되는 케이블 (충전 전용 케이블은 안 됨)
삼성 USB 드라이버developer.samsung.com → USB Driver (v1.9.5.0)
오딘 3.13.1신뢰할 수 있는 사본 — 이 글에서는 archive.org에 보존된 AFHArchive 사본을 사용하고 SHA256으로 무결성을 확인했다
순정 펌웨어samfrew.com에서 SM-T583 + 지역 KOO(한국 순정) 선택, 2.0GB
adb platform-tools백업과 다운로드 모드 진입용 (선택이지만 강력 추천)

현재 빌드명은 설정 → 태블릿 정보 → 소프트웨어 정보에서 확인한다. 이 기기의 마지막 웅진 빌드는 T583KOU1ASD1, 순정 최신은 T583KOU1BTG1(2020년 8월)이었다.

1단계. 자료 백업 — 여기서 함정 첫 번째

개발자 옵션(빌드번호 7회 탭) → USB 디버깅 ON → PC에서 adb devices로 연결 확인 후 adb pull로 폴더별 백업을 진행했다.

그런데 adb pull은 한글 파일명·폴더명을 깨뜨린다. 폴더 통째로 pull은 중간에 실패하고("Not a directory"), 한글 이름이 들어간 파일은 목적지를 못 만든다. tar 스트림으로 우회하려 해도 윈도우 bsdtar의 인코딩 처리 문제로 실패했다.

해법: 파일 단위 pull. adb shell의 find로 파일 목록을 뽑고, 파일마다 목적지 경로를 직접 지정해 pull하되 윈도우에서 금지된 문자(: 등)는 _로 치환, 원본 경로 매핑은 CSV로 기록했다. 끝나고 파일 개수를 반드시 대조했다 (381장 사진, 49개 문서 등 전수 일치 확인).

2단계. 펌웨어 내려받기

samfrew.com에서 SM-T583 / KOO / 최신 빌드를 고르면 zip 하나가 내려온다. 압축을 풀면 4개의 tar.md5가 나온다:

  • AP_... (시스템 본체, 2.4GB)
  • BL_... (부트로더)
  • CSC_... (공장초기화용 지역 설정)
  • HOME_CSC_... (데이터 유지용 지역 설정)

CP(통신 모뎀)는 LTE 모델에만 있고 WiFi 전용기에는 없다. 무결성은 오딘이 로드할 때 자동 검증하므로, 로드 시 "Checking MD5 finished Sucessfully"가 뜨면 된다.

3단계. 오딘 준비 — 함정 두 번째

오딘 압축을 풀고, 삼성 USB 드라이버를 먼저 설치한다. 설치하지 않으면 다운로드 모드에서 오딘이 COM 포트를 못 본다.

참고로 이 드라이버 인스톨러는 /S 무음 설치 옵션이 동작하지 않는다(종료 코드 8). UI로 직접 Next → Install → Finish를 눌렀다.

4단계. 다운로드 모드 진입

  • 방법 A (가장 확실): USB 디버깅이 켜져 있으면 adb reboot download 한 줄.
  • 방법 B: 전원을 끈 뒤 볼륨상 + 볼륨하를 동시에 누른 채 USB 케이블을 연결(기종별로 콤보가 다르다) → 경고 화면에서 볼륨상 1회.

성공하면 장치관리자에 "SAMSUNG Mobile USB CDC Composite Device"가 뜨고, 오딘 왼쪽 위 로그에 <ID:0/004> Added!!, 포트가 초록색으로 표시된다.

5단계. 오딘 플래싱

  1. BL 슬롯: BL_...tar.md5
  2. AP 슬롯: AP_...tar.md5 — 2.4GB라 로드에 몇 분 걸린다 (MD5 검증 포함)
  3. CSC 슬롯: 이 글은 웅진 흔적 완전 제거가 목적이라 공장초기화용 CSC를 넣었다. 데이터를 유지하고 싶으면 HOME_CSC를 쓴다.
  4. Options: Auto Reboot 체크, Nand Erase는 해제.
  5. Start → 3~5분 → 좌측 상단에 초록 PASS.

첫 부팅은 "앱 최적화" 때문에 5~10분 걸릴 수 있으니 인내하자.

6단계. 함정 세 번째 — 설정 마법사가 구글에서 막힌다 (이 글의 핵심)

순정 펌웨어로 올렸는데, 설정 마법사에서 구글 계정을 연결하는 순간 "이 계정에서는 구글 어시스턴트를 사용할 수 없습니다" 오류가 뜨고 진행이 완전히 멈춘다. 2020년 빌드의 마법사와 2026년 구글 서버 사이의 호환성 문제다. 뒤로 가기로도 안 풀리고, 강제 재부팅해도 같은 단계에서 다시 막힌다.

해법:

  1. 전원 끈 상태에서 전원 + 볼륨상 길게 → 리커버리 진입
  2. 볼륨 버튼으로 이동, 전원 버튼으로 선택 → Wipe data/factory reset → Yes
  3. 재부팅 → 마법사가 다시 시작되면 이번엔 구글 로그인 단계에서 "건너뛰기" → 이후 단계도 전부 건너뛰기 → 홈 화면 도달

교훈: 액자·키오스크 용도라면 구글 계정은 처음부터 넣지 말자. (참고로 2025년 4월부터 구글은 개인 개발자의 사진 라이브러리 읽기 API를 차단해서, 구글포토 연동 액자 앱들도 이 시점부터 동작이 깨졌다. 로컬 사진 방식이 더 오래간다.)

7단계. 개발자 옵션 재활성화

플래싱으로 초기화되면 개발자 옵션도 사라진다. 빌드번호 7회 탭부터 다시 → USB 디버깅 ON → 연결 후 나타나는 RSA 허용 팝업에서 허용.

마무리 — 벽돌에서 액자로

이후는 자유다. 이 기기는 자작 홈 런처 앱을 얹어 거실 사진 액자로 재탄생했다 (홈 앱 지정, 밤 11시~아침 7시 화면 자동 어둡게, 충전 중 화면 꺼짐 방지, PC에서 사진 동기화).

전체 소요 시간: 백업 20분 + 펌웨어 다운로드 3시간 + 오딘 세팅 30분 + 플래싱 5분 + 마법사 트러블슈팅 20분.

빠른 체크리스트

  •  사진·문서 백업 (개수 대조까지)
  •  삼성 USB 드라이버 설치
  •  펌웨어 KOO 최신 빌드 다운로드
  •  adb reboot download → 오딘 BL/AP/CSC → Start → PASS
  •  마법사: 구글 건너뛰기 (계정 넣지 말 것!)
  •  개발자 옵션 + USB 디버깅 재설정