PUI Launcher 화자 등록 Overlay 연동 구현 설계
이 문서는 SKMLauncher2의 setLauncherScreen과 VitalSign PUI 진입 구조를 실제
코드로 추적하고, ondevice 앱의 기존 화자 관리·등록 화면을 PUI 버튼과
DeviceAgent 이벤트에서 안전하게 여는 구현안을 정리한다.
현재 상태: 이 페이지는 구현 전 설계 기준선이다. 기존 ondevice overlay와 등록 엔진은 코드에 존재하지만, Launcher용 Receiver·service action·결과 callback은 아직 구현하지 않았다. UI 가이드가 제공되면 4장의 교체 가능 영역을 확정한 뒤 이 계약을 기준으로 구현한다.
핵심 결론은 다음과 같다.
- VitalSign 화면은 런처 내부 Fragment가 아니라 별도 패키지
com.gbsoft.vitalsign의 launch Activity다. - PUI 버튼과
setLauncherScreen(screenName=vitalSign)은SKMLauncherApp.launchVitalSignApp()으로 합쳐진다. - ondevice 앱에는 이미 화자 관리 overlay, 등록 overlay, 녹음 전환, WAV 저장, embedding 등록 로직이 존재한다.
- 현재 빠진 것은 PUI에서 이 내부 화면으로 들어가는 보호된 앱 간 진입점이다.
- 1차 구현은 UI와 마이크를 ondevice에 유지하고, 런처가
explicit component + signature permission으로 화면 표시만 요청하는 방식이 가장 작고 안전하다.
버전 주의: 분석한 MR6 SKMLauncher2 소스는
0.277, 연결 기기192.168.123.118:5555의 설치본은0.285다. 구조적 결론은 유효하지만 실제 반영 전0.285소스에서 동일 callback과 화면 정책을 대조해야 한다.
1. 현재 구현에서 확인된 사실
1.1 PUI VitalSign 버튼
apps/SKMLauncher2/.../ui/stay/StayActivity.java:92-98
binding.btnVitalSign.setOnClickListener(
__ -> ((SKMLauncherApp) getApplication()).launchVitalSignApp("PUI")
);
PUI 버튼은 런처 화면을 교체하지 않고 SKMLauncherApp의 공통 실행 함수로
넘긴다.
1.2 DeviceAgent setLauncherScreen
apps/SKMLauncher2/.../ui/SKMLauncherApp.java:1428-1436
method == setLauncherScreen
-> outdata.screenName
-> LauncherScreen.fromScreenName()
-> launchScreenByLlm()
LauncherScreen.java에는 VITAL_SIGN("vitalSign")이 있고,
launchScreenByLlm()의 VITAL_SIGN 분기는
launchVitalSignApp("LLM")을 호출한다.
따라서 진입 주체만 다를 뿐 다음 두 경로는 같은 함수로 수렴한다.
PUI button -> launchVitalSignApp("PUI")
LLM callback -> setLauncherScreen(vitalSign) -> launchVitalSignApp("LLM")
1.3 VitalSign은 별도 앱
SKMLauncherApp.java:2072-2095는 다음을 수행한다.
- Privacy mode를 확인한다.
com.gbsoft.vitalsign설치 여부를 확인한다.- 현재 공기청정, interaction schedule, 이동을 정지한다.
getLaunchIntentForPackage("com.gbsoft.vitalsign")를 얻는다.START_VITAL_SIGN_BY=PUI|LLMextra를 넣고 Activity를 실행한다.
연결 기기에서도 다음이 확인됐다.
| 앱 | 설치 위치 | 버전 |
|---|---|---|
| SKMLauncher | /system/priv-app/SKMLauncher/SKMLauncher.apk |
0.285 |
| VitalSign | /system/priv-app/VitalSign/VitalSign.apk |
1.6.7 |
| ondevice | /system/priv-app/MaumAi/MaumAi.apk |
1.19.36 |
VitalSign main component는 com.gbsoft.vitalsign/.MainActivity다.
1.4 ondevice에는 등록 화면이 이미 있다
ForegroundService.kt의 현재 구현:
| 코드 | 역할 |
|---|---|
isSpeakerRegisterCommand() |
STT 결과에 화자인식 포함 여부 확인 |
showSpeakerManageOverlay() |
등록, 목록, 뒤로가기 관리 화면 표시 |
showSpeakerRegisterOverlay() |
이름 입력, 녹음, 초기화, 전체 등록 화면 표시 |
startSpeakerEnrollment() |
기존 STT 녹음 정지 후 등록용 AudioRecord 획득 |
stopSpeakerEnrollment() |
WAV 저장과 embedding 등록 후 STT 복구 |
SpeakerListActivity |
등록 화자 목록 |
SpeakerHistoryActivity |
화자별 기록 |
등록 중 마이크 소유권도 이미 처리한다.
resumeSttAfterEnrollment = AudioRecordManager.isRecording()
-> stopContinuousRecording()
-> requestAudioRecord(speakerEnrollmentClientId)
-> 등록 녹음
-> stopSpeakerEnrollment()
-> restoreSttAfterEnrollment()
즉 새로 만들어야 할 핵심은 등록 UI 자체가 아니라 외부에서 내부 UI를 열 수 있는 계약이다.
1.5 지금 바로 직접 실행할 수 없는 이유
ondevice manifest에서 다음 컴포넌트는 모두 exported=false다.
.ui.SpeakerListActivity.ui.SpeakerHistoryActivity.service.ForegroundService
이는 외부 앱이 임의로 진입하지 못하게 하는 올바른 기본값이다. 런처에서 현재 Activity 이름을 직접 start하거나 service component를 호출하는 방식으로 풀면 권한과 수명주기 계약이 무너진다.
2. 권장 목표 구조
2.1 책임 배치
| 영역 | 책임 |
|---|---|
| PUI / SKMLauncher2 | 버튼, setLauncherScreen screen name, 현재 화면·도메인 정책, 진입 요청 |
| 앱 간 entrypoint | 호출자 제한, explicit intent, 중복 요청과 초기화 상태 검증 |
| ondevice ForegroundService | 기존 overlay 표시, 마이크 전환, embedding 등록 |
| DeviceAgent / TaskManager | LLM 경로가 필요할 때 queue, timeout, terminal status |
2.2 ondevice 변경
com.skmagic.ondeviceai.permission.SPEAKER_ENROLLMENT를protectionLevel=signature로 선언한다.- 이 권한으로 보호된
SpeakerEnrollmentReceiver를 만든다. - 런처는 package가 아니라 Receiver component까지 지정한 explicit intent를 보낸다.
- Receiver는 기존
ForegroundService에ACTION_SHOW_SPEAKER_MANAGE를 전달한다. - 서비스는 main thread에서 기존
showSpeakerManageOverlay()를 호출한다. - overlay 추가 성공, 이미 표시 중, 권한 없음, 서비스 미초기화를 각각 구분해 로그와 결과로 남긴다.
권장 action 예시:
com.skmagic.ondeviceai.action.SHOW_SPEAKER_MANAGE
com.skmagic.ondeviceai.action.SHOW_SPEAKER_REGISTER
현재 코드를 그대로 재사용하는 기준안은 관리 화면부터 여는 것이다. 등록, 목록,
뒤로가기의 현재 UX를 유지할 수 있고 향후 삭제·동의 화면도 같은 entrypoint 아래
확장할 수 있다. 다만 PUI가 곧바로 “등록 씬”을 요구하면
targetScreen=REGISTER로 직접 등록 overlay를 열 수 있다. 최종 기본값은 UI
가이드에서 결정하고 IPC 계약은 두 진입을 모두 지원한다.
2.3 SKMLauncher2 변경
LauncherScreen에 다음 값을 추가한다.
SPEAKER_REGISTRATION("speakerRegistration")
PUI 버튼과 LLM 경로는 반드시 같은 함수로 수렴시킨다.
PUI button
-> launchSpeakerEnrollment("PUI")
setLauncherScreen(speakerRegistration)
-> launchScreenByLlm()
-> launchSpeakerEnrollment("LLM")
launchSpeakerEnrollment()은 다음을 책임진다.
- Privacy mode와 화면 충돌 정책 확인
- ondevice package/component 존재 확인
- explicit intent + signature permission 전송
- 전송 성공과 실제 overlay 표시 완료를 구분
2.4 완료 시점
현재 launchScreenByLlm()은 switch 실행 직후
reportScreenApplied(appliedScreenName)를 호출한다. 외부 앱/overlay 연동에서는
intent 전송 성공이 화면 표시 성공과 같지 않다.
따라서 speakerRegistration은 다음 중 하나로 완료를 닫아야 한다.
- ondevice가 overlay
WindowManager.addView()성공 뒤 결과 callback 전송 - 요청에 correlation ID를 넣고 DeviceAgent가 terminal result를 기다림
- 최소 구현에서는 timeout과 실패 상태를 둔 explicit result receiver 사용
REQUEST_ACCEPTED != SCREEN_APPLIED != ENROLLMENT_COMPLETED
제품 정책상 어떤 상태를 setLauncherScreenStatus=success로 볼지 먼저 정해야
잘못된 조기 완료를 막을 수 있다. 화면 전환 task라면 SCREEN_APPLIED, 등록
workflow라면 ENROLLMENT_COMPLETED가 terminal 기준이다.
3. 상세 구현 계약
3.1 런타임 시퀀스
외부 진입에서는 다음 경계를 지킨다.
- Launcher는
requestId를 생성하고 explicit broadcast를 보낸다. SpeakerEnrollmentReceiver는 권한과 필수 extra만 검증한다.- UI 처리는 Receiver에서 하지 않고 기존
ForegroundServiceaction으로 전달한다. - Service는 main thread에서
WindowManager.addView()를 수행한다. addView()성공 뒤에만SCREEN_APPLIED를 반환한다.- 사용자 등록 성공, 취소 또는 실패 시 Service가 overlay를 제거하고 terminal result를 한 번만 반환한다.
- Launcher는 terminal result 또는 timeout 뒤 기존 PUI 화면을 복구한다.
Receiver는 짧게 끝나야 한다. 마이크 획득, 파일 저장, embedding 생성처럼 시간이 걸리는 처리는 현재처럼 Service가 소유한다.
3.2 앱 간 message contract v1
아래 이름은 구현 시 상수 파일로 고정할 제안값이다. UI 문구나 색상과 독립적인 IPC 계약이므로 UI 가이드가 바뀌어도 유지한다.
| 항목 | 제안값 | 설명 |
|---|---|---|
| open action | com.skmagic.ondeviceai.action.SPEAKER_ENROLLMENT_OPEN |
Launcher → ondevice |
| event action | com.skmagic.ondeviceai.action.SPEAKER_ENROLLMENT_EVENT |
ondevice → Launcher |
| protocol | protocolVersion=1 |
호환되지 않는 버전은 거부 |
| correlation | requestId |
요청부터 terminal result까지 동일한 UUID |
| source | entrySource=PUI, LLM, INTERNAL |
닫기·복귀 정책 선택 |
| target | targetScreen=REGISTER, MANAGE |
처음 표시할 overlay |
| event | REQUEST_ACCEPTED, SCREEN_APPLIED, COMPLETED, CANCELLED, FAILED |
단계 또는 종료 상태 |
| reason | reasonCode |
기계 판독 가능한 실패·거부 사유 |
보안과 개인정보 원칙:
- Launcher는 ondevice의 Receiver component를 명시한다.
- open Receiver와 result Receiver 모두
com.skmagic.ondeviceai.permission.SPEAKER_ENROLLMENTsignature permission으로 보호한다. - 양쪽 앱 manifest에 동일 permission 사용 관계를 명시하고 platform certificate를 배포 전 비교한다.
- 결과 broadcast에는 사용자 표시 이름, 음성 샘플 경로, embedding을 넣지 않는다.
speakerProfileId가 필요하면 내부 식별자만 선택적으로 반환한다.requestId, event, reason, elapsed time만 운영 로그에 남기고 음성·이름은 마스킹한다.
권장 reason code:
| reasonCode | 의미 | 재시도 |
|---|---|---|
PRIVACY_MODE |
Privacy 정책상 진입 불가 | 정책 해제 후 가능 |
SCREEN_CONFLICT |
VitalSign/Security 등 충돌 화면 | queue 또는 나중에 재시도 |
SERVICE_NOT_READY |
ForegroundService 초기화 전 | 가능 |
OVERLAY_PERMISSION_DENIED |
SYSTEM_ALERT_WINDOW 사용 불가 |
시스템 설정 필요 |
ALREADY_VISIBLE |
동일 overlay 표시 중 | 기존 요청에 attach |
ALREADY_RECORDING |
등록 녹음 진행 중 | 기존 요청 유지 |
AUDIO_ACQUIRE_FAILED |
등록용 AudioRecord 획득 실패 | 가능 |
NO_VALID_SAMPLE |
유효 샘플 없음 | 재녹음 |
EMBEDDING_FAILED |
profile 생성·저장 실패 | 원인 확인 후 가능 |
TIMEOUT |
Launcher가 terminal result를 받지 못함 | 상태 조회 후 가능 |
3.3 Service 내부 명령
외부 Receiver가 private ForegroundService에 전달할 action은 앱 내부 상수로
분리한다.
ACTION_SHOW_SPEAKER_ENROLLMENT
requestId
entrySource
targetScreen
ACTION_DISMISS_SPEAKER_ENROLLMENT
requestId
dismissReason
onStartCommand()은 action을 기존 handleAction()으로 넘기므로, 여기에
화자 등록 action 분기만 추가하면 된다. Receiver가 Service의 private 함수를
직접 호출하거나 Activity를 실행하지 않는다.
Service가 보관해야 할 최소 session:
data class SpeakerEnrollmentSession(
val requestId: String,
val entrySource: EntrySource,
val targetScreen: TargetScreen,
val startedAtElapsedMs: Long,
var state: EnrollmentUiState,
var terminalResultSent: Boolean = false,
)
requestId가 같은 재전송은 idempotent하게 처리하고, 다른 요청이 이미 진행
중이면 정책에 따라 기존 session에 attach하거나 ALREADY_RECORDING으로 거부한다.
3.4 UI·녹음 상태 머신
| 상태 | 표시/처리 | 가능한 다음 상태 |
|---|---|---|
IDLE |
overlay 없음 | SHOW_REQUESTED |
SHOW_REQUESTED |
main thread에 표시 예약 | VISIBLE, FAILED |
VISIBLE |
이름 입력 및 녹음 시작 가능 | RECORDING, CANCELLED |
RECORDING |
STT 정지, 등록 AudioRecord 독점 | FINALIZING, CANCELLED, FAILED |
FINALIZING |
WAV 저장, embedding/profile 등록 | COMPLETED, FAILED |
COMPLETED |
성공 결과 1회 전송 | DISMISSING |
CANCELLED |
취소 결과 1회 전송 | DISMISSING |
FAILED |
실패 결과 1회 전송 | DISMISSING 또는 재시도 가능한 VISIBLE |
DISMISSING |
IME 숨김, removeView(), session 정리 |
IDLE |
모든 종료 경로에서 다음 순서를 보장한다.
등록 녹음 중지
→ AudioRecord callback 해제
→ 필요 시 STT 소유권 복구
→ IME 숨김
→ WindowManager.removeView()
→ terminal result 1회 전송
→ session 정리
현재 onDestroy()는 녹음 중지와 등록 overlay 제거를 수행하지만 관리 overlay
제거와 결과 callback은 없다. 구현 시 두 overlay를 모두 정리하고 진행 중
외부 요청에는 FAILED/SERVICE_DESTROYED를 반환하도록 보강한다.
3.5 entrySource별 닫기·뒤로가기
현재 showSpeakerRegisterOverlay()의 닫기와 뒤로가기는 모두 다음과 같다.
stopSpeakerEnrollment()
→ hideSpeakerRegisterOverlay()
→ showSpeakerManageOverlay()
외부 PUI 진입에 그대로 사용하면 사용자가 닫기를 눌러도 다른 ondevice overlay가 다시 열리므로 자연스럽지 않다. 목표 동작은 다음처럼 분리한다.
| entrySource | 등록 화면 뒤로가기 | 닫기 | 등록 완료 |
|---|---|---|---|
INTERNAL |
관리 overlay 복귀 | 관리 overlay 복귀 | 관리 overlay 복귀 또는 성공 표시 |
PUI |
모든 overlay 닫기 + CANCELLED |
동일 | 모든 overlay 닫기 + COMPLETED, PUI 복귀 |
LLM |
모든 overlay 닫기 + CANCELLED |
동일 | 모든 overlay 닫기 + COMPLETED, task 완료 |
Android system back, 화면 닫기 버튼, timeout, service destroy도 동일한
finishEnrollmentSession() 경로로 수렴시켜 terminal result 중복을 막는다.
3.6 Launcher 완료 의미
두 완료 단계를 섞지 않는다.
SCREEN_APPLIED
= overlay가 실제 WindowManager에 추가됨
= setLauncherScreen 작업의 성공 후보
COMPLETED
= 유효 샘플 저장 + embedding/profile 등록 성공
= 화자 등록 workflow의 성공
PUI 버튼은 SCREEN_APPLIED 뒤 사용자 상호작용을 기다리고, LLM/TaskManager가
“화자 등록 완료”까지 요구한 경우에만 COMPLETED를 terminal 기준으로 사용한다.
화면 표시 timeout과 등록 workflow timeout은 서로 다른 timer로 둔다.
4. UI 가이드 반영 계약
UI 가이드가 제공되면 기존
app/src/main/res/layout/overlay_speaker_register.xml과 관련 drawable,
color, string을 교체해 구현할 수 있다. 통신·오디오·상태 머신과 View 렌더링을
분리하면 시안 변경이 기능 로직에 영향을 주지 않는다.
4.1 현재 UI 기준선
현재 코드에서 확인된 값:
| 항목 | 현재 구현 |
|---|---|
| Window type | Android O 이상 TYPE_APPLICATION_OVERLAY |
| 크기 | 화면 너비 100%, 화면 높이의 90% 이상 |
| 위치 | 화면 중앙 |
| 입력 | 한국어 locale의 단일 행 화자 이름 EditText |
| 주요 동작 | 녹음 시작/종료 toggle |
| 보조 동작 | 뒤로가기, 닫기, 전체등록, 초기화 |
| 안내 문구 | 동일 이름 5회, 녹음별 5초 이상 권장 |
| keyboard | overlay 표시 후 이름 입력에 focus하고 IME 표시 |
이는 기능 검증용 기준선이며 최종 제품 UI 확정값은 아니다.
4.2 UI 가이드에서 받아야 할 항목
| 영역 | 필요한 가이드 | 미제공 시 임시 기준 |
|---|---|---|
| 화면 규격 | 해상도, 방향, safe area, PUI 헤더 유지 여부 | 현재 기기 display metrics |
| 배경 | dim 강도, modal/card/full-screen 여부 | 현재 overlay 배경 유지 |
| typography | font family, 크기, weight, 줄간격 | 기존 speaker style |
| 색상 | surface, primary, warning, disabled, focus | 기존 resource 유지 |
| 버튼 | 순서, 크기, primary/secondary, icon | 현재 ID를 유지하고 skin 교체 |
| 문구 | 제목, 안내, 동의, 오류, 완료 | 기존 한국어 문구 + TBD |
| 녹음 표현 | timer, waveform, 단계, 남은 횟수 | 시작/종료 text toggle |
| 성공/실패 | toast, inline, 완료 scene, 자동 닫기 시간 | 결과 후 즉시 닫기 제안 |
| keyboard | 화면 키보드/음성 입력, focus 정책 | 현재 Android IME |
| 접근성 | focus order, content description, 대비, 글자 확대 | Android 기본 + 검증 필요 |
| motion | 등장/퇴장, 상태 전환 duration | 150~250ms fade 제안 |
| 정책 버튼 | 전체등록·초기화 노출 대상 | 일반 PUI에서는 숨김 제안 |
특히 전체등록과 초기화는 개발·관리 성격이 강하므로 일반 사용자 PUI에
노출할지 제품 정책 확인이 필요하다. UI 가이드 전에는 삭제하지 않고
visibility 정책으로 분리한다.
4.3 UI 상태별 화면 산출물
UI 가이드는 최소 다음 상태를 포함하면 그대로 구현 가능하다.
- 최초 진입/이름 입력
- 이름 미입력 validation
- 녹음 준비
- 녹음 중
- 녹음 처리 중
- 등록 성공
- 재시도 가능한 실패
- 취소 확인이 필요한 경우
- 중복 이름 또는 기존 profile 처리
- Privacy/마이크 사용 안내
각 화면에 normal, pressed, focused, disabled 상태와 최종 문구가 있으면 XML resource와 상태 renderer로 옮긴다. Figma, SVG/PNG, 해상도 스펙, color/font token 중 제공 가능한 형식으로 전달하면 된다.
4.4 변경에 강한 구현 경계
SpeakerEnrollmentReceiver / IPC contract
│ 변경하지 않음
SpeakerEnrollmentSession + state reducer
│ UI 상태만 전달
SpeakerEnrollmentOverlayController
│ inflate / render / dismiss
XML + drawable + color + string
│ UI 가이드에 따라 교체
버튼 listener 안에서 직접 저장·결과 전송을 모두 처리하지 않고
onRecordClicked, onBackClicked, onCloseClicked 이벤트를 controller로
보내면 UI 레이아웃이 바뀌어도 기능 동작을 재사용할 수 있다.
5. Receiver와 Activity 중 무엇이 맞나
| 안 | 장점 | 제약 | 현재 판단 |
|---|---|---|---|
| 보호된 Receiver + 기존 overlay | 기존 코드 재사용, 변경량 작음, 빠른 실기기 검증 | Window overlay 수명주기를 서비스가 계속 관리 | 1차 구현 추천 |
| 전용 exported Activity + bound service | back stack, IME, 접근성, lifecycle이 자연스러움 | 서비스 private 등록 상태를 controller로 분리해야 함 | 장기 제품 UX 추천 |
| Launcher에 등록 UI 복제 | Launcher 단독 UX 가능 | 녹음/저장/embedding 책임 중복, IPC 확대 | 비추천 |
| 기존 비공개 Activity를 단순 export | 구현은 짧음 | 호출자·상태·권한 계약 부실 | 비추천 |
| 보호 없는 implicit broadcast | 연결이 쉬움 | 제3자 호출과 spoofing 가능 | 금지 |
기존 Phase 1 문서는 장기 제품형 구조로 Launcher-native UX + ondevice 등록 엔진 경계를 제안했다. 이번 코드 발견으로 현재 제품 브랜치의 빠른 통합 경로는 ondevice overlay 재사용이 더 현실적이라는 사실이 추가됐다. 두 설계는 경쟁안이 아니라 단계가 다르다.
Phase 1A: protected entrypoint + existing ondevice overlay
Phase 1B: enrollment controller/API 분리
Phase 1C: Launcher-native product UX + bound service/AIDL
6. 화면·오디오·TaskManager 정책
6.1 화면 충돌
진입 전에 최소한 다음 상태를 결정해야 한다.
- VitalSign 실행 중
- Security mode 화면 실행 중
- 제품 이동 중
- Privacy mode
- 다른 overlay 표시 중
- 화자 등록이 이미 진행 중
정책은 allow, queue, reject 중 하나로 명시하고 reason code를 남긴다.
6.2 마이크와 STT
현재 등록 구현은 STT 녹음을 중지하고 등록 client가 AudioRecord를 독점한 뒤 복구한다. 따라서 STT와 등록 녹음을 동시에 수행하는 새 fan-out은 필요하지 않다.
다만 다음 종료 경로에서도 반드시 복구돼야 한다.
- 등록 완료
- 사용자 취소/뒤로가기
- overlay 제거
- timeout
- service destroy
- AudioRecord 획득 실패
- embedding 등록 예외
6.3 화자인식 추론 구조와의 연결
등록 화면은 화자 embedding을 생성해 profile에 저장하는 producer다. 실제 음성
turn에서는 CPU STT와 QNN speaker identification이 병렬 실행되고 hard join 뒤
VOICE_ID를 확정하는 consumer 구조로 연결된다.
PUI 등록
-> speaker profile / embedding 저장
일반 발화
-> CPU STT ----------------------┐
-> QNN speaker identification ---┤ hard join
-> text + VOICE_ID
-> profile/personalization lookup
자세한 추론 구조는 CPU STT + QNN 화자인식 병렬 통합 문서와 Speaker Context Contract에서 이어서 본다.
7. 구현 순서
- UI 가이드의 확정 항목과 TBD를 4장 표에 채우고 PUI 최초 화면을
REGISTER또는MANAGE로 결정한다. - 설치본
0.285와 MR60.277의setLauncherScreen, completion, domain policy 차이를 대조한다. - ondevice에 signature permission, explicit Receiver, service action, idempotency guard를 추가한다.
- session/state reducer와 source별 닫기 정책을 단위 테스트한다.
- adb explicit intent로 ondevice 단독 진입과 STT stop/restore를 검증한다.
- SKMLauncher에
speakerRegistrationenum과 공통 launch 함수를 추가한다. - PUI 버튼 경로를 연결한다.
- DeviceAgent callback allowlist와 TaskDomainResourcePolicy를 확장한다.
REQUEST_ACCEPTED,SCREEN_APPLIED,CANCELLED,FAILED,TIMEOUT을 실기기 log로 검증한다.- 제품 UX가 확정되면 overlay를 전용 Activity/controller 구조로 단계적으로 옮긴다.
8. 최소 검증 매트릭스
| 구분 | 케이스 | 합격 조건 |
|---|---|---|
| 보안 | 권한 없는 앱이 intent 전송 | Receiver 실행 거부 |
| 진입 | PUI 버튼 | 요청한 targetScreen overlay 1회 표시 |
| 진입 | setLauncherScreen=speakerRegistration |
같은 target 정책으로 overlay 표시 |
| 중복 | 빠른 연속 요청 | overlay 중복 생성 없음 |
| 오디오 | 등록 시작 | 일반 STT 녹음 중지, 등록 client 획득 |
| 오디오 | 완료/취소/실패 | STT 녹음 복구 |
| 화면 | VitalSign/Security/Privacy 충돌 | 정책대로 queue 또는 reason 포함 거부 |
| lifecycle | service 재시작 중 요청 | crash 없이 retryable failure |
| 완료 | intent만 수신 | terminal success로 조기 보고하지 않음 |
| 완료 | overlay add 성공 | SCREEN_APPLIED 확인 |
| 등록 | embedding 저장 성공 | 목록과 다음 화자 식별 turn에서 사용 가능 |
| 회귀 | 일반 WUW/F2/STT | 등록 미사용 시 기존 동작 유지 |
| UI | PUI 진입 후 닫기 | 관리 overlay가 다시 뜨지 않고 PUI 복귀 |
| UI | INTERNAL 진입 후 뒤로가기 | 기존 관리 overlay 복귀 |
| 결과 | success/cancel/error 경합 | requestId당 terminal result 정확히 1회 |
| 복구 | process/service destroy | overlay 잔존 없음, STT 또는 실패 결과 정리 |
| 개인정보 | result/log 검사 | 이름·WAV 경로·embedding 외부 노출 없음 |
8.1 구현 완료 정의
다음 조건을 모두 충족해야 “연동 구현 완료”로 판단한다.
- Launcher PUI 버튼과
setLauncherScreen(speakerRegistration)이 같은 공통 함수로 수렴한다. - 권한 없는 앱의 open broadcast가 거부된다.
- 실제 기기에서 ondevice 등록 overlay가 1회만 표시된다.
- PUI와 INTERNAL 진입의 닫기 동작이 설계대로 다르다.
- 등록 성공 후 profile이 저장되고 다음 화자인식 turn에서 조회된다.
- 완료·취소·오류·timeout 모두 STT와 overlay 자원을 복구한다.
- platform certificate가 설치본과 일치하는 release APK로 검증한다.
- UI 가이드 상태 화면과 실제 screenshot을 대조한다.
9. 최종 판단
구현은 가능하며 새 화면을 처음부터 만드는 작업보다 작다. 현재 ondevice가 이미 UI와 녹음/등록 상태를 소유하므로, PUI/Launcher는 화면 선택과 정책만 담당하고 ondevice의 보호된 entrypoint를 호출하는 구조가 1차 목표에 맞다.
제품형 Launcher-native UX는 이후 controller/API를 분리한 다음 진행한다. 지금 바로 UI와 AudioRecord를 Launcher로 옮기면 이미 동작하는 등록 경로를 복제하고 앱 간 PCM 전달, 취소, timeout, 저장 책임까지 동시에 새로 설계해야 하므로 리스크가 커진다.
현재 문서는 구현에 착수할 수 있는 계약 수준까지 정리됐으며, UI 가이드에서 미정인 항목은 4장에 격리했다. 이후 UI 가이드가 제공되면 화면 리소스와 renderer 부분을 확정하고, 사용자가 구현을 요청하는 시점에 Launcher와 ondevice 두 저장소의 코드를 이 기준으로 변경한다.