DeviceAgent 기능별 실행과 상태·저장소
기준:
1398d109c391c4044cae26d0e9aea4d3f08d9670. 지정 커밋의 추적 파일을 직접 확인한 정적 구조 분석이며, 현재 HEAD·후속 TaskManager·SKIX 구현을 포함하지 않는다.
모든 상대 소스 경로는 apps/DeviceAgent/app/src/main/java/com/sk/airbot/deviceagent/ 기준이다.
2026-09-16 분석 정정: 아래 청정 정책 개요는 계산 의도를 요약한 설명이다. 활성 코드는 self-test TODO, 청정 진행
false고정, 최종POLICY_NOT_DEFINED반환이므로 계산한 mPolicy가 호출자에게 그대로 전달되지 않는다. 실제 반환값과 호출자 분기를 함께 확인해야 한다.
청정·이동·지도
BaseCleaningManager가 청정 조정자다. 생성 시 BaseCleaningManager HandlerThread를 시작하고(cleaning/BaseCleaningManager.java:54-60), set(AirCleaningParameter)에서 새 START 여부를 판별한 뒤 AirCleaningPolicy.getPolicy를 호출한다(cleaning/BaseCleaningManager.java:202-248). 정책은 홈 잠금, 배터리, self/line test, 정거장·이동 여부와 area 존재를 검사해 즉시 시작, 기존 청정 stop 후 재시작, 차단을 구분한다(cleaning/AirCleaningPolicy.java:56-196).
범위가 BASIC이면 BasicCleaningManager, 위치·스캔 범위이면 MovingCleaningManager를 같은 HandlerThread에 구성한다(cleaning/BaseCleaningManager.java:261-314). 상태 업데이트는 START/STOP/PAUSE/RESUME/RETURN으로 분기하고(cleaning/BaseCleaningManager.java:327-416), 동일 action에서는 풍량 변경만 반영한다(cleaning/BaseCleaningManager.java:418-434). START는 보고서 버퍼를 비우고 현재 air-quality snapshot을 넘긴다(cleaning/BaseCleaningManager.java:437-476).
BasicCleaningManager.start는 시작 시각과 보고서 시작을 기록하고 fan/monitor를 시작한다. startFanStopChecker는 최대 청정시간·예약 종료·auto 전환을 같은 HandlerThread의 지연 작업으로 감시한다(cleaning/BasicCleaningManager.java:60-105, :219-254). pause/resume은 monitor와 fan-stop 검사기를 중단·재개하며(:301-326), recordCleaningEndReport가 after snapshot을 채운다(:344-368). 실제 fan·센서 완료는 AirQualityManager와 MCU 계층에 의존한다.
MovingCleaningManager는 상태 머신을 HandlerThread에서 돌린다. area 목록, 현재 index, 반환·보고서 플래그를 보유하고(cleaning/MovingCleaningManager.java:31-65), transition runnable이 전체 area 검사→이동→도착→청정→복귀→도킹/충전→보고로 이어진다(:298-428). moveToArea와 clean은 각각 MovingLocationManager와 BasicCleaningManager를 호출한다(:620-643). onLocationMovingStatus/onLocationArrived가 obstacle, docking, charging, arrived를 반영한다(:929-1004). pause/resume은 예약 transition과 이동을 멈추고 다시 전이한다(:807-918).
AreaAICleaningManager는 split-space별 센서 측정과 회전을 담당한다. 1초 주기로 값을 모으고 360도 회전 뒤 평균을 계산하며(cleaning/AreaAICleaningManager.java:36-49, :319-409), obstacle/movement error를 retry하고 최악 위치를 선택한다(:443-547). 정적 연결은 callback 기반이나 센서값 유효성·물리 회전 완료는 장치 검증이 필요하다.
RobotMovementController는 USER/SYSTEM/EMERGENCY를 MoveRequest로 포장하고 queue lock 아래 중복·우선순위를 판정한다(moving/RobotMovementController.java:254-284, :413-443). emergency가 우선하며 진행 중 emergency가 있으면 낮은 요청을 버린다. 일반 이동은 lock/privacy mode에서 early return한다(:255-258). station 복귀는 청정을 먼저 정지하고 이전 Mode를 보존한다(:300-357). MovingLocationManager callback의 도착·도킹·충전은 논리 통지이며 주행 완료 증거가 아니다(:198-252).
MapManager.init은 keystore 설정에 따라 암호화 metadata를 서비스에서 읽거나 SD 카드 JSON을 internal backup으로 복사한다(map/MapManager.java:69-104). area는 SKMPreferences에 저장하고(:147-180, :239-250), station ID는 0으로 고정한다(:69). nearby 목록은 전체 area 순서를 그대로 반환하며 근접 정렬은 TODO다(:252-278). getAreaListById 계열도 TODO다(:330-357).
deleteMap은 schedule 전체를 삭제하고 map 파일/암호화 파일을 제거한 뒤 coordinates와 공기질 연동 설정을 초기화하고 AMRMap.opInitMapData를 호출한다(map/MapManager.java:353-386). 매핑 카운트다운은 MappingCountDownHandlerThread에서 5부터 0까지 1초 간격으로 main callback을 보내며(map/MappingManager.java:16-29, :68-119), IS_STARTMAPPING이 true면 시작을 거부한다(:68-75). MapUploader는 property flag가 켜졌을 때만 외부 mapupload.UploadService로 A1 정보, pgm/yaml, 위치, 완료·취소 Intent를 보낸다(map/MapUploader.java:23-57, :76-125). key 조회는 EncryptionService bind에 의존하며 실제 업로드 성공은 확인하지 않는다.
WSS·Streaming·EventAsset
WssManager는 WssIpcThread와 background/move/schedule Handler를 구성한다(wss/WssManager.java:260-264, :373-450). start/stop/pause/resume 요청을 TaskExecutor task로 직렬화하고(:568-603, :721-750, :857-885), start 시 availability/policy 검사 후 area를 정규화해 WssTransaction에 저장한다(:637-696). 시작 순서는 patrol 이동, 30초 후 vision detection, LLM stop, EventAsset capture start, running state 갱신이다(:693-717).
stop은 LCD 복구, handler·end-time·patrol 취소, vision 재활성화, area listener 해제, moving manager destroy, EventAsset capture 중지, verify 초기화 후 STOP을 확정한다(wss/WssManager.java:789-853). pause는 START에서만 허용하고 LCD/vision을 일반 상태로 되돌린다(:909-929). 여러 handler/thread가 상태를 만지며 running-state lock은 있으나 모든 callback이 단일 thread라는 보장은 없다.
StreamingManager는 StreamingIpcThread와 StreamingWorker를 생성한다(streaming/StreamingManager.java:63-95). start는 TaskExecutor를 통해 LLM·vitalsign 상태를 검사하고(:162-192), precondition 통과 시 모든 action을 중단한 뒤 STARTING과 Front start bundle을 만든다(:195-217). Front 성공 응답 때만 START, vision off, tilting, LCD always-on, off-station manual mode를 적용한다(:219-247). stop은 STOPPING과 Front stop 요청을 먼저 보내고 응답으로 START/STOP을 확정한다(:249-306). 업로드·public-key·movement 결과는 callback bundle 전달이며(:308-347), request 반환 성공은 queue 수락 수준이다.
WssEventAssetManager는 EventAsset client, state, verify controller, upload controller를 조합한다(wss/eventasset/WssEventAssetManager.java:20-45). 연결되지 않은 startCapture는 bind만 요청하고 종료한다(:62-69). 유효한 WSS detection에만 request ID를 만들고 기존 세션이 있으면 거부한다(:91-111, :219-234). verify 성공은 upload가 이미 요청됐거나 clip이 없으면 세션만 닫고, clip이 있으면 deleteClipAsset으로 연결한다(:122-140; WssEventAssetVerifyController.java:44-64). 실패/timeout은 upload controller로 가며 URL expiry·공개키·correlation ID가 함께 관리된다(:142-178; WssUploadController.java:118-178).
WssUploadRecoveryController는 네트워크 연결, IoT가 전달한 AWS/network ready 상태, EventAsset bind+callback 준비를 모두 요구한다(wss/WssUploadRecoveryController.java:13-15, :198-240). ready 직후 delay를 두고, recovery upload 완료 뒤 다음 후보를 이어서 찾는다(:144-180). local network 연결만으로 IoT event dispatch나 업로드 성공을 뜻하지 않는다.
설정·유지보수·알림·Provider
Settings는 SettingPreferences를 주 저장소로 사용하고 fan time 일부는 SKMPreferences를 사용한다(settings/Settings.java:73-86, :1105-1144). initSettings는 volume/mute 순서, language/temperature, air-quality link, LED/night mode를 읽어 AudioManager·LED·LCD·LLM에 적용한다(:86-186). setter는 저장과 side effect를 함께 수행하며 volume은 main looper debounce를 거친다(:605-784). IOTGetConfig는 현재 설정을 Bundle로 만들어 IoT command로 보낸다(:1244-1387). 자동 재부팅 시각은 system property와 alarm 경로에 있으나 AOSP 재부팅 완료는 미확인이다.
DeviceUsageManager는 battery, fan/LCD, motor distance/time, lidar/ToF/camera/sensor, mic/speaker 사용량을 SKMPreferences 문자열로 lazy initialize/read한다(maintenance/DeviceUsageManager.java:8-165). 증가·집계 writer는 이 클래스에 없다. FilterUsageManager.java는 스냅샷에서 0 byte로 별도 구현이 없다. PartReplacementManager는 부품 key 목록과 날짜를 저장하며 허용 목록 key만 현재 날짜로 reset한다(:13-55).
ErrorHistoryManager는 외부 content://com.sk.airbot.provider.error/errors provider에 ERROR/SUCCESS를 insert/update한다(maintenance/ErrorHistoryManager.java:21-94). resolved는 같은 code query의 첫 row를 사용하며 최신 미해결 보장은 이 소스에 없다. EventLoggingManager는 /mnt/data3/logs/events에 ERROR/BOOT/REBOOT/BASIC_CLEAN/MOVING_CLEAN/SELFTEST/OTA_UPGRADE 이벤트 파일을 쓴다(maintenance/EventLoggingManager.java:14-35); 권한·보존은 미확인이다.
AlertDao는 app-private alert.db SQLite를 singleton으로 열고 timestamp index를 만든다(alert/AlertDao.java:16-43). insert 후 timestamp DESC 기준 최대 200개로 trim하며(:46-69), Bundle 조회는 최근 30일·최대 200개다(:100-150). AlertDbHelper.onUpgrade는 table DROP 후 재생성하므로 보존 migration이 아니다(alert/AlertDbHelper.java:31-41).
DeviceAgentProvider는 preferences와 preferences/* URI만 match한다(provider/DeviceAgentProvider.java:16-28). query는 SettingPreferences 우선, SKMPreferences fallback인데(:38-55), insert/update는 모든 값을 SKMPreferences에 저장한다(:64-105). 따라서 SettingPreferences key를 update하는 경우 저장소 불일치 가능성이 있다. exported/permission과 caller authorization은 이 파일만으로 확정하지 않았다.
Cleaning report
CleaningReportManager는 location index의 before/after air-quality, 청정 모드, scanning/AI flags, outdoor/air-sensor 정보와 notification set을 메모리에 보유한다(report/CleaningReportManager.java:21-34, :466-520). 동일 index는 before/after deep copy로 갱신하고 새 항목은 deep copy로 추가한다(:824-844). PM/HCHO/NOX/CO2 condition에 따라 notification ID/priority를 만든다(:521-550).
toBundle은 before/after가 유효한 location만 포함하고 중첩 air-quality Bundle, duration, notifications를 만든다(report/CleaningReportManager.java:604-674). duration은 hour/minute substring으로 분 계산하며 같은 분의 비동일 시각은 최소 1분 처리한다(:729-773). location bundle이 비면 빈 Bundle을 반환하고, 성공 시 내부 상태를 clear한다(:669-673, :809-822). saveReport는 CleaningReportDao에 insert한다(:676-678). 보고서 생성·DB 저장·IoT/Launcher 송신은 서로 다른 단계다.
OTA·update·calibration·interaction
OTAManager는 in-memory flag와 side effect만 가진다. enable은 LLM STOP과 SoundFlow OTA mode를 켜고, disable은 voice recognition 설정에 따라 LLM을 복구한다(ota/OTAManager.java:8-31). 패키지 검증·rollback·reboot 완료는 구현하지 않는다.
UpdateManager는 Handler 기반이다. AMR version을 1초 간격으로 확인해 유효 version이면 DeviceControlService에 OTAUpdateAMRStart를 보내고 120회 초과면 update app에 FAILED를 보낸다(update/UpdateManager.java:19-27, :54-67, :107-131). MCU/EyeLED도 version check를 수행하며 MCU status는 120초 지연 command를 보낸다(:133-164). 실제 firmware update와 reboot 완료는 외부 경계다.
SensorCalibrationManager는 sensitivity를 SettingPreferences에 저장하고 Settings.settingChanged를 호출한다(calibration/SensorCalibrationManager.java:56-82). 마지막 air-quality calibration이 14일보다 오래되면 scheduled run한다(:92-99, :151-162). air-quality calibration은 MCU CO2/fan cleaning 설정과 warm-up을 시작하고 main handler에서 10초 뒤 idle로 복귀하며 Launcher/IoT에 resultCalibration=done을 보낸다(:102-147). 날짜와 다음 alarm은 완료 callback 전에 갱신된다. LADAR branch는 TODO다(:110-114).
InterScheduleManager는 interaction schedule을 HandlerThread와 main handler로 수행한다. welcome weather/cleaning, wakeup morning-call/weather, relax 5종을 정의하며(interaction/InterScheduleManager.java:67-112), 이동은 스케줄 2분 전부터 시작하고 이동·현장 action timeout은 각 15분 정책이다(:127-171). MovingLocationManager와 ActionWelcome/ActionWakeup/ActionRelax를 조합한다. ActionBase는 자체 HandlerThread/Handler와 LLM·sub-action·notify callback을 보유한다(interaction/ActionBase.java:20-53). flow는 COUNT_DOWN→START_MOVE→ARRIVED→PERSON_DETECTED→START_ACTION→END_ACTION→RETURNING→DOCKING→CHARGING이다(InterScheduleManager.java:82-110). 사람 감지·음성 출력·AMR 도착은 외부 callback 없이는 증명되지 않는다.
결론과 미확인 경계
이 커밋의 DeviceAgent는 기능별 singleton/manager가 메모리 상태를 들고 HandlerThread, AlarmManager, ContentProvider, SQLite, SharedPreferences를 조합해 외부 모듈로 command를 전달한다. TaskExecutor queue 수락, callback Bundle 생성, transaction state 변경은 정적 확인 가능하다. AMR 주행·fan/MCU 물리 수행, EventAsset clip/upload·암호화, IoT/AWS ACK, Binder 권한·caller identity, APK/AOSP 설치와 OTA 완료는 이 소스의 근거로 주장할 수 없다.
확대 구조도와 연결별 근거
- 청정·이동·지도 · 실행 상태와 장치 연결 — 상세 페이지 · SVG 원본
- 예약·설정·유지보수 · 시간과 영속 데이터 — 상세 페이지 · SVG 원본
- WSS·Streaming · 전용 IPC와 세션 제어 — 상세 페이지 · SVG 원본
Connections
전체 목차 · 앱 구성 · DeviceAgent 내부 · IPC · 실행 흐름 · 플랫폼·빌드 · 근거·확인 범위