본문으로 건너뛰기

Android 어댑터 & 데모 앱

리포지토리의 demo/에는 Jetpack Compose로 SDK를 구동하는 디버그 월렛 앱이 있고, android/에는 SDK의 포트를 구현한 재사용 가능한 Android 플랫폼 어댑터 라이브러리가 있습니다. 앱은 데모 코드를 복사하는 대신 이 android/ 라이브러리에 의존하면 됩니다.

android/ 어댑터 라이브러리​

Maven 그룹 com.hopae.eudi.android 아래 세 모듈입니다(SDK의 com.hopae.eudi와 구분되어 아티팩트가 충돌하지 않습니다):

모듈제공주요 클래스
core1차 포트AndroidKeystoreSecureArea(SecureArea, 하드웨어 기반), FileStorageDriver(StorageDriver), OkHttpTransport(HttpTransport), FileTransactionLogStore(TransactionLogStore)
proximityISO 18013-5 트랜스포트BleGattClientTransport / BleGattServerTransport(ProximityTransport), NfcEngagementService(홀더 HCE), NfcReader(리더). 라이브러리 매니페스트가 BLE/NFC 권한 + HCE 서비스를 앱에 병합합니다
dcapiDigital Credentials APIDcApiRegistrar(Credential Manager 등록 + 매처), DcApiRequest / DcApiResult(엔벌로프 + 마샬링), DcApiBranding(OS 셀렉터 로고/브랜딩)

Wallet 조립​

모든 것은 WalletPorts로 호스트가 주입합니다 — DI 프레임워크 없음:

val logger = LogWalletLogger() // 당신의 WalletLogger (데모는 logcat + 화면 + 파일로 라우팅)
val wallet = Wallet.create(
config = WalletConfig(),
ports = WalletPorts(
secureAreas = listOf(AndroidKeystoreSecureArea()), // android/core — 하드웨어 키
storage = FileStorageDriver(File(filesDir, "wallet")), // android/core
http = OkHttpTransport(logger = logger), // android/core
transactionLogStore = FileTransactionLogStore(File(filesDir, "tx.log")), // android/core
logger = logger,
// walletAttestation = ... // WUA (Wallet Provider) — 아직 어댑터 없음; 어태스테이션 트랙 참고
),
)

포트 커버리지. android/ 라이브러리는 필수 포트(SecureArea, StorageDriver, HttpTransport) 전부와 TransactionLogStore, 근접 트랜스포트를 제공합니다. WalletClock / Rng는 SDK 기본값 (WalletClock.System / Rng.Default)을 씁니다. WalletLogger는 의도적으로 앱이 제공 — 실제 로깅 (화면/파일)은 앱별이라 android/core에 구체 로거를 넣지 않습니다. 아직 어댑터가 없는 유일한 포트는 WalletAttestationProvider(Wallet Unit Attestation)로, 어태스테이션 워크스트림에서 다룹니다.

근접 (BLE + NFC)​

트랜스포트를 만들어 파사드에 넘깁니다. BLE 양쪽 모드와 NFC static + negotiated 핸드오버가 구현·폰투폰 실기기 검증되어 있습니다(INTEROP.md 참고).

// BLE peripheral-server 홀더:
val server = BleGattServerTransport(context, uuid, Ble.PERIPHERAL_SERVER, retrievalMethods, logger = logger)
val session = wallet.proximity.present(server) // QR 인게이지먼트

// 리더:
val docs = wallet.reader.read(clientTransport, engagement, requested)

NFC의 경우 홀더는 NfcEngagementProcessor(static, 또는 negotiated hr → hs 셀렉터)로 HCE를 무장하고, 리더는 NfcReader.readHandover(...)가 static vs TNEP negotiated를 자동 감지합니다. negotiated에서는 홀더가 자기 BLE 역할을 강요하지 않고 리더가 제안한 것 중에서 고릅니다(NfcHandoverRequest.selectCarrier()) — BLE 모드는 누가 정하나 참고.

양쪽 역할 모두 탭 콜백을 받습니다 — readHandover(activity) { … }와 NfcEngagementService.onEngaged가 태그를 잡는 즉시, APDU 이전에 호출됩니다. 데모는 여기서 진동을 울립니다. 탭은 밀리초 만에 끝나는데 핸드오버와 BLE 연결은 그 뒤로 수 초가 걸리기 때문에, 이 확인이 없으면 사람들이 교환 도중에 폰을 떼어버립니다.

데모 기본값은 NFC negotiated handover, QR은 Central Bluetooth 역할입니다 — 둘 다 mdoc central client 모드로 귀결되며, 리더가 광고하고 지갑은 스캔만 합니다. Settings에서 바꿀 수 있습니다. 전체 수명주기는 demo/.../ui/ProximityScreens.kt와 근접 가이드, 그리고 손대고 나면 다시 돌려야 할 기기 테스트 매트릭스 demo/PROXIMITY-MATRIX.md를 참고하세요.

Digital Credentials API​

DcApiRegistrar.register(activity, wallet, DcApiBranding(logoPng = appIconPng()), logger = logger)

월렛의 크리덴셜을 Credential Manager(OS 크리덴셜 셀렉터)에 등록하고 앱 아이콘으로 브랜딩합니다. 라우팅된 인텐트는 DcApiRequest / DcApiResult로 얇은 액티비티에서 처리합니다(데모의 GetCredentialActivity 참고).

빌드​

데모는 SDK와 어댑터를 composite build(includeBuild("../kotlin") + includeBuild("../android"))로 소비합니다:

// demo/app/build.gradle.kts
implementation("com.hopae.eudi:wallet:0.0.1-SNAPSHOT")
implementation("com.hopae.eudi.android:core:0.0.1-SNAPSHOT")
implementation("com.hopae.eudi.android:proximity:0.0.1-SNAPSHOT")
implementation("com.hopae.eudi.android:dcapi:0.0.1-SNAPSHOT")
cd demo
./gradlew :app:assembleDebug # → app/build/outputs/apk/debug/app-debug.apk
adb install -r app/build/outputs/apk/debug/app-debug.apk

툴체인: AGP 9.2.1, Gradle 9.5, compileSdk 36, minSdk 29. android/ 빌드에는 android/local.properties에 sdk.dir=/path/to/android-sdk가 필요합니다.