개발자 정보

기여 가이드

코드를 고치기 전에 먼저 읽어 주세요. 골든룰, 권한, 서비스, SDK 사용 범위, 컨벤션, 테스트 시나리오, 알려진 TODO를 다룹니다.

동작이 조금이라도 바뀌는 변경은 레포의 SPEC.md를 먼저 봅니다 — 동작·데이터 계약· 아키텍처의 source of truth입니다.

골든룰

이건 어디서도 어기지 않습니다

  1. Samsung SDK를 지어내지 않습니다. SDK 호출은 wear/.../sensor/HeartRateSensorManager.kt 한 곳에 모았고 1.4.1에 대조했습니다. 대조 못 한 호출은 // TODO(SDK)로 두고 상수·타입·메서드명을 만들어 쓰지 않습니다.
  2. 저장 단계에서 raw 표본을 거르지 않습니다. bpm 0·off-wrist· 비정상 status·빈 컬럼도 다 그대로 둡니다. hr <= 0 필터는 시각화에서만 쓰고, Room·CSV에선 금지합니다.
  3. 워치 Room이 source of truth. 폰으로 보내기 전에 로컬에 먼저 씁니다. 전송 실패가 데이터 손실로 이어지면 안 됩니다.
  4. 비밀은 커밋하지 않습니다. local.properties·*.jks· *.keystore·keystore.properties·빌드 산출물은 git-ignore. (Samsung .aar는 비공개 레포 편의로 커밋 — 공개 전환 시 추적에서 뺍니다.)
  5. CSV 계약을 깨지 않습니다. 컬럼 순서 + sensor_session_YYYYMMDD_HHMMSS.csv 파일명은 공개 스펙.
  6. 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 .aarjavap로 대조한 범위만 씁니다:

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.
  • :wearwear/libs/samsung-health-sensor-api-1.4.1.aar가 있어야 컴파일.
  • ./gradlew :mobile:assembleDebug, ./gradlew :wear:assembleDebug(.aar 필요), ./gradlew lint.

검증 전엔 ‘된다’고 하지 않기

자동화 테스트가 없습니다. Samsung SDK는 실기기가 필요해서 실기기 수동 검증이 유일한 길입니다. 기기 테스트 없이 동작을 단정하지 말고 “컴파일됩니다 / 구현했습니다”까지만 말합니다.

테스트 시나리오 (수동, 실기기)

  1. 1분 수집 — 틱 수 ≈ 경과 초, tickIndex 단조 증가.
  2. 5분 실내 정지 — bpm 안정, 센서 warm-up 외 gap 없음.
  3. 10분 보행 — bpm이 운동량을 따라감.
  4. 화면 끄고 수집 — 카운트 계속(foreground service + wake lock).
  5. 세션 중 폰 끊기 → 워치 로컬 카운트 증가 → 재연결 → 폰이 누락분을 다시 받아 채움.
  6. 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을 열지 않습니다.