← Docs hub

ICT·ErrorProvider·StartupService — 시험·오류·부팅

기준: 1398d109c391c4044cae26d0e9aea4d3f08d9670. 지정 커밋의 추적 파일을 직접 확인한 정적 구조 분석이며, 현재 HEAD·후속 TaskManager·SKIX 구현을 포함하지 않는다.

ICT

진입점과 test UI

apps/ICT/app/src/main/AndroidManifest.xml:18-40은 Application QApplication, landscape LineTestActivity를 exported/singleTask/noHistory/excludeFromRecents로 선언하고 MAIN/LAUNCHER 진입으로 사용한다. QApplication.kt:13-30은 Application 생성 시 DeviceControlProxy 연결 callback을 등록한다. build Gradle은 DeviceControlAPIJavaLib.jar를 implementation으로 포함한다(app/build.gradle:69-72), 따라서 DeviceControl API가 ICT의 IPC 경계다.

LineTest 화면은 ui/linetest/LineTestActivity.kt, connection prepare/done/process/result Fragment, LineTestViewModel.kt, Result adapter/model로 구성된다. ViewModel state는 LineTestState, LineTestUiEvent, ResultState, TestResult, ItemResult를 통해 화면에 전달된다.

Repository→IPC 호출

data/DeviceControlResolver.java:8-14는 연결되지 않았으면 null을 반환한다. ChargingRepository, DrivingRepository, EnvironmentSensorRepository, NoiseDrivingRepository, NoiseFanRepository, NoiseTiltingRepository, VoiceTestRepository, CommonRepository의 실제 메서드는 resolver에서 IDeviceControl을 얻어 sendModuleCommand(indata, outdata)를 호출한다. 예를 들어 CommonRepository.kt:22-38은 method=setMainState, state를 Bundle에 넣고 result를 읽으며, 42-58은 getMainState, 60-98은 ICTState serial number, 101-134는 Cliff IR JSON 파싱, 159-197은 ICT test start/stop, 255-284는 firmware version을 처리한다. 모든 RemoteException은 로그 후 빈 문자열/오류 데이터로 축약된다.

Driving repository는 이동/회전/정지 관련 명령, Charging은 station/charging, EnvironmentSensor는 센서 데이터, Noise*는 모터·팬·틸팅 소음 시험, VoiceTest는 voice test 명령을 담당한다. 따라서 ICT의 repository 계층은 자체 DB가 아니라 DeviceAgent module command RPC의 thin adapter다.

설비 socket과 threading

server/Ict.kt는 수신 command를 enum으로 dispatch해 repository 메서드와 UI 상태를 갱신한다. server/IctSocket.kt는 설비 통신을 담당하고, Ict.kt:292-320 부근의 connect-check는 main Looper Handler와 AtomicLong을 사용해 마지막 heartbeat 이후 50초 timeout 시 재연결 실패 UI로 전환한다. library/os/QHandler.kt는 Handler delayed message helper이고, library/net/Wifi.kt는 BroadcastReceiver로 Wi-Fi 상태를 받는다.

미확인: Ktor socket의 실제 bind/accept/설비 protocol 성공, DeviceAgent Binder service의 명령 적용, repository 호출이 UI thread인지 socket thread인지의 전체 경로, 시험 결과가 외부 설비에 반영되는지는 device/log 증거가 필요하다. Manifest의 system shared UID 및 network/location 권한도 실제 grant가 확인되어야 한다.

ErrorProvider

apps/ErrorProvider/AndroidManifest.xml:17-70은 package com.sk.airbot.providers.error, provider authority com.sk.airbot.provider.error, exported non-multiprocess syncable provider, cleanup service, PRE_BOOT OTA receiver, BOOT_COMPLETED receiver를 선언한다. XML에는 normal read/write permission과 signature|privileged인 ACCESS_ERROR_DATA를 자체 정의하고(24-41), uses-permission으로 모두 요청한다. Android.mk는 소스를 src 전체에서 가져와 priv-app으로 설치하고 private platform API/proguard를 설정한다(Android.mk:5-28).

src/.../ErrorProvider.java:34-99는 authority와 errors/errors/# URI matcher를 정의한다. SQLite DB errors.db, version 1, errors 테이블(code/type/priority/status/occurred/resolved timestamp)을 만든다(57-90). DatabaseHelper는 singleton, WAL, foreign key constraint를 사용하며 onCreate에서 테이블 생성, onUpgrade에서 기존 테이블 DROP 후 재생성한다(116-169). provider onCreate는 helper를 만들고(235-243), shutdown에서 close한다(247-251).

CRUD 흐름은 insert/query/update/delete에 URI matcher를 적용하고 SQLite DB를 사용한다. query 기본 sort는 occurred timestamp descending이며 cursor notification URI를 설정한다(ErrorProvider.java:284-318). insert/update/delete 후 ContentResolver.notifyChange()를 호출한다(268-281, 318-378 및 후속 구간). caller가 ACCESS_ERROR_DATA permission을 갖는지 검사하는 함수는 230-233에 있다. projection map은 DB column을 동적으로 보강해 OTA schema 변화에 대응한다(174-218).

ErrorDataCleanupService.java:43–53는 action을 검사한 뒤 끝나는 골격이며 실제 삭제·보존기간 처리는 없다. BootCompletedReceiver.java는 BOOT_COMPLETED에서 정리 동작을 시작한다. ErrorProviderUpgradeReceiver.java:41-55는 PRE_BOOT_COMPLETED에서 version preference를 읽어 upgrade handling을 수행한다. 이 모듈의 주요 저장소는 SQLite이며, 외부 UI/DeviceAgent는 content URI와 권한을 통해 오류를 읽고 쓴다.

미확인: provider 호출 caller 권한 부여, provider가 system image에 포함되는지, SQLite migration의 데이터 보존(현재 upgrade는 DROP), ContentResolver notify를 소비하는 실제 launcher observer는 제품 APK 조합이 필요하다.

StartupService

apps/StartupService/app/src/main/java/com/sk/airbot/startupservice/가 아래 Java 경로의 기준이다. Manifest는 system shared UID와 persistent app, BootReceiver, exported StartupService를 선언한다. receiver/BootReceiver.java:9–18이 BOOT_COMPLETED에서 startService를 호출한다.

StartupService.java:42–51의 onCreate는 foreground notification을 먼저 만들고 reset Handler와 팝업 검사를 초기화한다. startForegroundService():132–155는 notification channel을 만들고 startForeground를 호출하며, setCaptivePortalMode():158–161은 Settings.Global의 captive_portal_mode를 0으로 저장한다. onStartCommand는 START_STICKY를 반환하고 onBind는 null이다. Manifest의 IIotControl action만 보고 IoT Binder 서버라고 해석하면 안 된다.

공장 초기화 조건과 데이터

util/UpdatePreferences.java는 이름과 달리 SharedPreferences가 아니라 세 파일을 직접 읽고 쓴다. persist의 UpdatePreferences.json, OTA 경로의 UpdatePreferences.text, data2 update 경로의 marker가 입력이다.

  1. data2 marker가 있으면 현재 버전을 persist 파일에 기록하고 marker를 삭제한 뒤 이번 reset 경로를 건너뛴다(loadData2FromFile).
  2. data2 marker가 없고 persist 파일도 없으면 loadFromFile이 true를 반환한다.
  3. persist 파일이 있으면 oldVersion을 읽으며, OTA metadata의 factoryVersion보다 낮고 status가 true일 때 loadFromOtaFile이 reset 조건을 만족한다.
  4. 조건을 만족하면 overlay 팝업을 띄우고 현재 버전을 기록한 뒤 10초 Handler를 예약한다(StartupService.java:57–112).
  5. factoryReset은 android 패키지에 FACTORY_RESET broadcast를 보낸다(:116–121). 실제 wipe는 외부 Android framework의 책임이다.

onDestroy에는 super 호출만 있다. 예약 Handler 제거와 overlay 제거가 이 경로에는 없으므로 재기동·종료 시점 검토가 필요하다. foreground 전환 코드는 확인했지만 부팅 시 permission grant, 파일 접근, 실제 초기화와 OTA 이후 persistence는 검증하지 않았다.

확대 구조도와 연결별 근거

Connections

전체 목차 · 앱 구성 · DeviceAgent 내부 · IPC · 실행 흐름 · 플랫폼·빌드 · 근거·확인 범위

Keyboard shortcuts

⌘K / Ctrl+KOpen command palette
/Focus search
g hGo to home
g pGo to projects
g sGo to sessions
j / kNext / prev row (tables)
?Show this help
EscClose dialogs

Structured queries

Mix key:value filters with free text in the palette:

type:sessionOnly session pages
project:llm-wikiFilter by project name (substring)
model:claudeFilter by model name (substring)
date:>2026-03-01Sessions after a date
date:<2026-04-01Sessions before a date
tags:rustPages mentioning a tag/topic
sort:dateSort results by date (newest first)

Example: type:session project:llm-wiki date:>2026-04 sort:date