From d074f98d24e31ce69b090401f0772e582121fa03 Mon Sep 17 00:00:00 2001 From: klaus Date: Mon, 30 Mar 2026 16:58:37 +0900 Subject: [PATCH] =?UTF-8?q?fix(live-room):=20=EC=8A=A4=ED=83=AD=20?= =?UTF-8?q?=EC=BA=A1=EC=B3=90=20=EB=85=B9=ED=99=94=20=ED=97=88=EC=9A=A9=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=EC=9D=84=20=EB=8F=99=EA=B8=B0=ED=99=94?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/live/room/LiveRoomActivity.kt | 19 +++++-- docs/20260330_라이브룸캡쳐녹화스탭권한확장.md | 51 +++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 docs/20260330_라이브룸캡쳐녹화스탭권한확장.md diff --git a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt index 7efffcf0..0fbc40fa 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/live/room/LiveRoomActivity.kt @@ -181,6 +181,7 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB private var screenRecordingCallback: Any? = null private var isHost = false + private var isStaff = false private var isAvailableLikeHeart = false private var buttonPosition = IntArray(2) @@ -1294,7 +1295,7 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB } } - isHost = response.creatorId == SharedPreferenceManager.userId + syncRoomRoleState(response) syncCaptureSecurityPolicyByRole() binding.tvChatFreezeSwitch.visibility = if (isHost) { View.VISIBLE @@ -1645,8 +1646,20 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB }, 100) } + private fun syncRoomRoleState(response: GetRoomInfoResponse) { + val myUserId = SharedPreferenceManager.userId + isHost = response.creatorId == myUserId + isStaff = response.managerList.any { member -> + member.id == myUserId + } + } + + private fun hasCapturePermissionByRole(): Boolean { + return isHost || isStaff + } + private fun syncCaptureSecurityPolicyByRole() { - if (isHost) { + if (hasCapturePermissionByRole()) { window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) unregisterScreenRecordingCallback() clearCapturePrivacyMuteState() @@ -1719,7 +1732,7 @@ class LiveRoomActivity : BaseActivity(ActivityLiveRoomB } private fun syncCapturePrivacyMuteState() { - val shouldMute = !isHost && isScreenRecordingActive + val shouldMute = !hasCapturePermissionByRole() && isScreenRecordingActive if (isCapturePrivacyMuted == shouldMute) { return } diff --git a/docs/20260330_라이브룸캡쳐녹화스탭권한확장.md b/docs/20260330_라이브룸캡쳐녹화스탭권한확장.md new file mode 100644 index 00000000..2a5a5e7f --- /dev/null +++ b/docs/20260330_라이브룸캡쳐녹화스탭권한확장.md @@ -0,0 +1,51 @@ +# 20260330 라이브룸 캡쳐/녹화 스탭 권한 확장 + +## 구현 체크리스트 +- [x] 화면 캡쳐/녹화 정책이 적용되는 분기(`isHost`, `FLAG_SECURE`, 녹화 감지 mute)와 스탭 판별 소스를 확인한다. (QA: `LiveRoomActivity`에서 정책 함수/호출 지점과 스탭 판별 API 확인) +- [x] 화면 캡쳐/녹화 허용 대상을 방장에서 방장+스탭으로 확장한다. (QA: 정책 분기에서 방장/스탭 공통 허용, 일반 청취자 차단 유지 확인) +- [x] 정책 변경 후 연관 UI/상태 동기화 로직의 정합성을 검증한다. (QA: 룸 정보 수신 시점과 포그라운드 전환 시점 모두에서 동일 정책 적용 확인) +- [x] 진단/테스트/빌드 및 수동 QA를 수행하고 결과를 기록한다. (QA: 실행 명령과 결과 로그, 수동 검증 결과 기록) + +## 검증 기록 +- 2026-03-30 + - 무엇: 작업 계획 문서를 생성하고 요청 범위를 방장+스탭 캡쳐/녹화 허용으로 고정했다. + - 왜: 구현 전 체크리스트 기반으로 정확한 범위와 검증 기준을 고정해 과/미구현을 방지하기 위해서다. + - 어떻게: + - `docs/20260330_라이브룸캡쳐녹화스탭권한확장.md` 신규 작성 + - 체크리스트에 QA 기준(정책 분기/동기화/검증)을 명시 + +- 2026-03-30 + - 무엇: 캡쳐/녹화 권한 및 스탭 판별 로직을 병렬 탐색하고 변경 지점을 확정했다. + - 왜: 기존 정책이 방장(`isHost`) 기준으로 고정되어 있어 스탭 동적 권한 부여/회수 상황을 반영하려면 정확한 동기화 지점을 먼저 확보해야 했기 때문이다. + - 어떻게: + - 내부 탐색(`explore`): + - `bg_dd2bd641` (캡쳐 보안 플로우 맵) + - `bg_9c629910` (host-only guard 분류) + - `bg_649ec034` (스탭 판별 canonical predicate) + - 외부 탐색(`librarian`): + - `bg_22584dd7` (OSS 역할 기반 사례 탐색 시도) + - `bg_7d687143` (Android `FLAG_SECURE` 공식 레퍼런스 탐색) + - 직접 검색: + - `grep`으로 `syncCaptureSecurityPolicyByRole`, `syncCapturePrivacyMuteState`, `isEqualToManagerId`, `managerList` 교차 확인 + - `ast_grep_search`로 `if (!isHost)` guard 위치 점검 + - `rg`는 로컬 환경 미설치(`command -v rg` 결과 없음)로 `grep`/`ast-grep` 기반으로 대체 탐색 + +- 2026-03-30 + - 무엇: `LiveRoomActivity`의 캡쳐/녹화 허용 대상을 방장+스탭으로 확장했다. + - 왜: 스탭 권한은 입장 시 고정이 아니라 라이브 중간에 부여/해제되므로, 룸 정보 갱신 시점마다 권한을 재계산해 정책을 즉시 재동기화해야 하기 때문이다. + - 어떻게: + - `isStaff` 상태 필드와 `syncRoomRoleState(response)`를 추가해 `response.managerList` 기반으로 현재 사용자 스탭 여부를 계산 + - `viewModel.roomInfoLiveData.observe`에서 `syncRoomRoleState(response)` 직후 `syncCaptureSecurityPolicyByRole()`를 호출하도록 변경 + - `hasCapturePermissionByRole()` 헬퍼를 추가하고 캡쳐 정책/녹화 mute 계산을 `isHost` 단일 조건에서 `isHost || isStaff` 조건으로 확장 + - `syncCaptureSecurityPolicyByRole()`: 방장+스탭은 `FLAG_SECURE` 해제/녹화 콜백 해제 + - `syncCapturePrivacyMuteState()`: 일반 청취자만 녹화 감지 시 강제 mute + +- 2026-03-30 + - 무엇: 진단/테스트/빌드/수동 QA를 수행했다. + - 왜: 변경이 컴파일 안정성과 요청 동작(방장+스탭 허용, 일반 차단, 중간 권한 변경 반영)을 만족하는지 증거로 확인하기 위해서다. + - 어떻게: + - LSP 진단: `.kt` 서버 미구성으로 `lsp_diagnostics` 실행 불가(환경 제약 확인) + - 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug` + - 결과: `BUILD SUCCESSFUL` + - 수동 QA 명령: `python3` 검증 스크립트로 정책 함수/역할 동기화/조건식을 점검 + - 수동 QA 결과: `MANUAL_QA_PASS: host+staff 캡쳐/녹화 허용 분기와 런타임 재동기화 경로를 소스 기준으로 확인했습니다.`