동작이 조금이라도 바뀌는 변경은 레포의 SPEC.md를 먼저 봅니다 — 동작·데이터 계약·
아키텍처의 source of truth입니다.
골든룰
이건 어디서도 어기지 않습니다
- Samsung SDK를 지어내지 않습니다. SDK 호출은
wear/.../sensor/HeartRateSensorManager.kt한 곳에 모았고 1.4.1에 대조했습니다. 대조 못 한 호출은// TODO(SDK)로 두고 상수·타입·메서드명을 만들어 쓰지 않습니다. - 저장 단계에서 raw 표본을 거르지 않습니다. bpm
0·off-wrist· 비정상status·빈 컬럼도 다 그대로 둡니다.hr <= 0필터는 시각화에서만 쓰고, Room·CSV에선 금지합니다. - 워치 Room이 source of truth. 폰으로 보내기 전에 로컬에 먼저 씁니다. 전송 실패가 데이터 손실로 이어지면 안 됩니다.
- 비밀은 커밋하지 않습니다.
local.properties·*.jks·*.keystore·keystore.properties·빌드 산출물은 git-ignore. (Samsung.aar는 비공개 레포 편의로 커밋 — 공개 전환 시 추적에서 뺍니다.) - CSV 계약을 깨지 않습니다. 컬럼 순서 +
sensor_session_YYYYMMDD_HHMMSS.csv파일명은 공개 스펙. - wire 포맷은 양쪽을 같이 고칩니다.
WearDataLayerClient(writer)와MobileDataLayerListenerService(reader)의 키·JSON 스키마는 함께 움직입니다.
권한
워치(AndroidManifest):
BODY_SENSORS, BODY_SENSORS_BACKGROUND, FOREGROUND_SERVICE,
FOREGROUND_SERVICE_HEALTH, FOREGROUND_SERVICE_LOCATION, WAKE_LOCK,
ACTIVITY_RECOGNITION, POST_NOTIFICATIONS,
ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, com.samsung.permission.SSENSOR
런타임에는 첫 세션 전에 BODY_SENSORS + ACTIVITY_RECOGNITION(+ API 33+
POST_NOTIFICATIONS)을 요청합니다. 거부하면 기록이 시작되지 않고 UI가
이유를 띄웁니다. 폰은 POST_NOTIFICATIONS(실행 시 요청)와
BLUETOOTH_CONNECT(선언만, optional)를 씁니다.
Foreground service & power
foregroundServiceType="health|location"선언.startForegroundService+ “Heart rate recording in progress” 저중요도 상시 알림(실시간 틱 수 표시).PARTIAL_WAKE_LOCK을 start 때 잡고 모든 종료 경로 (stopRecording·onDestroy)에서 풉니다. 4시간 안전 타임아웃.START_NOT_STICKY. stop: 트래커 중지 → 마지막 flush → 세션 행 마감 → 최종 메타 전송 →stopForeground(REMOVE)→stopSelf().
Samsung SDK 사용 범위
1.4.1 .aar에 javap로 대조한 범위만 씁니다:
HealthTrackingService(ConnectionListener, Context)
+ connectService() / disconnectService()
ConnectionListener.onConnectionSuccess / Ended / Failed
getHealthTracker(HealthTrackerType.HEART_RATE_CONTINUOUS)
HealthTracker.setEventListener(TrackerEventListener)
TrackerEventListener.onDataReceived(List<DataPoint>) / onError / onFlushCompleted
DataPoint.getValue(ValueKey.HeartRateSet.{HEART_RATE, HEART_RATE_STATUS, IBI_LIST}) 개발 중에는 워치에서 Health Platform 개발자 모드를 켜 두어야 합니다 (H. 설치·빌드 §3-A).
컨벤션
- Kotlin, Compose(폰 M3 · 워치 Wear Compose), Coroutines/Flow.
- 의존성은 버전 카탈로그
gradle/libs.versions.toml에서libs.*로 끌어 씁니다. 모듈 빌드 파일에 버전을 박지 않습니다. - 경량 MVP —
Activity → ViewModel → Repository → Room. DI도 클린 아키텍처 레이어도 없으니, 새 코드도 같은 수준으로 맞춥니다. - 주변 스타일을 따라가고, 주석은 꼭 필요한 데만 붙입니다.
빌드 & 검증
- JDK 17+ (번들 JBR / JDK 21). 에뮬레이터 AVD
atela_phone(Pixel 7,android-34) 준비됨. 설치 패키지는ai.atela.heartratecollector(.mobile아님), 런치는ai.atela.heartratecollector/.mobile.MainActivity. :wear는wear/libs/samsung-health-sensor-api-1.4.1.aar가 있어야 컴파일../gradlew :mobile:assembleDebug,./gradlew :wear:assembleDebug(.aar 필요),./gradlew lint.
검증 전엔 ‘된다’고 하지 않기
자동화 테스트가 없습니다. Samsung SDK는 실기기가 필요해서 실기기 수동 검증이 유일한 길입니다. 기기
테스트 없이 동작을 단정하지 말고 “컴파일됩니다 / 구현했습니다”까지만 말합니다.
테스트 시나리오 (수동, 실기기)
- 1분 수집 — 틱 수 ≈ 경과 초, tickIndex 단조 증가.
- 5분 실내 정지 — bpm 안정, 센서 warm-up 외 gap 없음.
- 10분 보행 — bpm이 운동량을 따라감.
- 화면 끄고 수집 — 카운트 계속(foreground service + wake lock).
- 세션 중 폰 끊기 → 워치 로컬 카운트 증가 → 재연결 → 폰이 누락분을 다시 받아 채움.
- Stop → 폰에서 Export CSV → 헤더 + 행 수가 세션과 일치.
알려진 TODO
- IBI는 DataPoint당 마지막 값만 저장. 비트별 전체(IBI_LIST)는 TODO.
- 워치 영속 재전송 큐 없음. DataClient 자동 동기화에 의존.
- release 서명 완료(업로드 키스토어·서명 AAB·SHA-256). Play 비공개 테스트로 배포 중(테스터 등록제). 정식 출시·Samsung Health 파트너 승인은 추후.
- BODY_SENSORS_BACKGROUND 필요성은 target OS별로 재평가.
- onDataChanged 대용량 배치 처리 성능 검토(현재 ~1초 배치라 실용 이슈 없음).
Git
- 지정된 feature 브랜치에서 작업합니다. 메시지는 명확하게, push는 요청받을 때만.
- 명시적 요청 없이 PR을 열지 않습니다.