Coverage Summary for Class: PermissionResultAggregator (kr.open.library.simple_ui.xml.permissions.result)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| PermissionResultAggregator |
92.9%
(13/14)
|
65.6%
(21/32)
|
88.7%
(63/71)
|
94.1%
(287/305)
|
| PermissionResultAggregator$Companion |
|
| PermissionResultAggregator$WhenMappings |
|
| Total |
92.9%
(13/14)
|
65.6%
(21/32)
|
88.7%
(63/71)
|
94.1%
(287/305)
|
package kr.open.library.simple_ui.xml.permissions.result
import kr.open.library.simple_ui.core.extensions.trycatch.safeCatch
import kr.open.library.simple_ui.core.logcat.Logx
import kr.open.library.simple_ui.core.permissions.classifier.PermissionClassifier
import kr.open.library.simple_ui.core.permissions.model.OrphanedDeniedRequestResult
import kr.open.library.simple_ui.core.permissions.model.PermissionDecisionType
import kr.open.library.simple_ui.core.permissions.model.PermissionDeniedItem
import kr.open.library.simple_ui.core.permissions.model.PermissionDeniedType
import kr.open.library.simple_ui.core.permissions.queue.PermissionQueue
import kr.open.library.simple_ui.xml.permissions.coordinator.RequestEntry
import kr.open.library.simple_ui.xml.permissions.state.PermissionStateSnapshot
import kr.open.library.simple_ui.xml.permissions.state.PermissionStateStore
/**
* Aggregates permission results, waiters, and persistence updates.<br><br>
* 권한 결과 집계, 대기자 관리, 상태 저장 업데이트를 담당합니다.<br>
*
* @param stateStore Saved state store used for persistence.<br><br>
* 상태 저장에 사용하는 스토어입니다.<br>
* @param stateSnapshot Snapshot reference for the persisted state.<br><br>
* 보존된 상태의 스냅샷 참조입니다.<br>
* @param queue Queue of request identifiers.<br><br>
* 요청 식별자 큐입니다.<br>
* @param requests Map of active request entries.<br><br>
* 활성 요청 엔트리 맵입니다.<br>
* @param inFlightWaiters Waiter map for merging permission results.<br><br>
* 권한 결과 병합을 위한 대기자 맵입니다.<br>
* @param classifier Permission classifier used for logging.<br><br>
* 로깅에 사용하는 권한 분류기입니다.<br>
*/
internal class PermissionResultAggregator(
private val stateStore: PermissionStateStore,
private val stateSnapshot: PermissionStateSnapshot,
private val queue: PermissionQueue,
private val requests: MutableMap<String, RequestEntry>,
private val inFlightWaiters: MutableMap<String, MutableSet<String>>,
private val classifier: PermissionClassifier,
) {
/**
* Log tag used for permission diagnostics.<br><br>
* 권한 진단 로그에 사용하는 태그입니다.<br>
*/
companion object {
/**
* Log tag used for permission diagnostics.<br><br>
* 권한 진단 로그에 사용하는 태그입니다.<br>
*/
private const val LOG_TAG = "PermissionRequester"
}
/**
* Returns and clears orphaned denied results after process restore.<br><br>
* 프로세스 복원 후 orphaned 거부 결과를 반환하고 비웁니다.<br>
*
* @return Return value: list of orphaned denied request results. Log behavior: none.<br><br>
* 반환값: orphaned 거부 요청 결과 목록입니다. 로그 동작: 없음.<br>
*/
fun consumeOrphanedDeniedResults(): List<OrphanedDeniedRequestResult> {
val results = stateSnapshot.orphanedResults.toList()
stateStore.update { snapshot -> snapshot.orphanedResults.clear() }
return results
}
/**
* Registers a request entry and persists its state.<br><br>
* 요청 엔트리를 등록하고 상태를 저장합니다.<br>
*
* @param entry Request entry to register.<br><br>
* 등록할 요청 엔트리입니다.<br>
*/
fun registerRequest(entry: RequestEntry) {
requests[entry.requestId] = entry
persistRequest(entry)
}
/**
* Registers waiter mappings for the given permissions.<br><br>
* 지정된 권한에 대한 대기자 매핑을 등록합니다.<br>
*
* @param requestId Request identifier to register.<br><br>
* 등록할 요청 식별자입니다.<br>
* @param permissions Permissions that should wait for results.<br><br>
* 결과를 대기할 권한 목록입니다.<br>
*/
fun registerWaiters(requestId: String, permissions: List<String>) {
permissions.forEach { permission ->
inFlightWaiters.getOrPut(permission) { mutableSetOf() }.add(requestId)
}
}
/**
* Completes waiters for [permissions] with the same [result].<br><br>
* [permissions]의 대기자를 동일한 [result]로 완료 처리합니다.<br>
*
* @param permissions Permissions to complete.<br><br>
* 완료 처리할 권한 목록입니다.<br>
* @param result Result to apply to all permissions.<br><br>
* 모든 권한에 적용할 결과입니다.<br>
*/
fun completeWaitersForPermissions(
permissions: List<String>,
result: PermissionDecisionType,
) {
if (permissions.isEmpty()) return
completeWaiters(permissions.associateWith { result })
}
/**
* Completes waiters using a permission-to-result mapping.<br><br>
* 권한-결과 매핑을 사용하여 대기자를 완료 처리합니다.<br>
*
* @param results Permission-to-result mapping.<br><br>
* 권한-결과 매핑입니다.<br>
*/
fun completeWaiters(results: Map<String, PermissionDecisionType>) {
if (results.isEmpty()) return
results.forEach { (permission, result) ->
val waiters = inFlightWaiters.remove(permission) ?: return@forEach
waiters.forEach { requestId ->
updateResultsForWaiters(
requestId = requestId,
permission = permission,
result = result,
)
}
}
}
/**
* Attempts to complete a request if all results are available.<br><br>
* 모든 결과가 준비되었으면 요청 완료를 시도합니다.<br>
*
* @param requestId Request identifier to complete.<br><br>
* 완료할 요청 식별자입니다.<br>
*/
fun tryCompleteRequest(requestId: String) {
val entry = requests[requestId] ?: return
if (!entry.isCompleted()) return
val deniedResults = entry.permissions.mapNotNull { permission ->
val decision = entry.results[permission] ?: PermissionDecisionType.MANIFEST_UNDECLARED
decision.toDeniedTypeOrNull()?.let { deniedType ->
PermissionDeniedItem(permission, deniedType)
}
}
if (entry.onDeniedResult != null) {
safeCatch { entry.onDeniedResult.invoke(deniedResults) }
} else if (deniedResults.isNotEmpty()) {
stateStore.update { snapshot ->
snapshot.orphanedResults.add(
OrphanedDeniedRequestResult(
requestId = requestId,
deniedResults = deniedResults,
),
)
}
}
requests.remove(requestId)
queue.remove(requestId)
stateStore.update { snapshot -> snapshot.requestStates.remove(requestId) }
}
/**
* Logs a permission result with request metadata.<br><br>
* 요청 메타데이터와 함께 권한 결과를 로깅합니다.<br>
*
* @param requestId Request identifier for the result.<br><br>
* 결과에 해당하는 요청 식별자입니다.<br>
* @param permission Permission string being logged.<br><br>
* 로깅할 권한 문자열입니다.<br>
* @param result Permission decision type to log.<br><br>
* 로깅할 권한 결정 타입입니다.<br>
*/
fun logResult(
requestId: String,
permission: String,
result: PermissionDecisionType,
) {
logPermissionResult(
requestId = requestId,
permission = permission,
result = result,
)
}
/**
* Updates [requestId] entry with [permission] result and attempts completion.<br><br>
* [requestId] 엔트리에 [permission] 결과를 갱신하고 완료 처리를 시도합니다.<br>
*
* @param requestId Request identifier to update.<br><br>
* 갱신할 요청 식별자입니다.<br>
* @param permission Permission being updated.<br><br>
* 결과를 갱신할 권한입니다.<br>
* @param result Result to store for the permission.<br><br>
* 권한에 저장할 결과입니다.<br>
*/
private fun updateResultsForWaiters(
requestId: String,
permission: String,
result: PermissionDecisionType,
) {
val entry = requests[requestId] ?: return
entry.results[permission] = result
persistRequest(entry)
logPermissionResult(
requestId = requestId,
permission = permission,
result = result,
)
tryCompleteRequest(requestId)
}
/**
* Persists the current state of [entry] to the saved state store.<br><br>
* [entry]의 현재 상태를 저장소에 반영합니다.<br>
*
* @param entry Request entry to persist.<br><br>
* 저장할 요청 엔트리입니다.<br>
*/
private fun persistRequest(entry: RequestEntry) {
stateStore.update { snapshot ->
snapshot.requestStates[entry.requestId] = entry.toState()
}
}
/**
* Logs permission denied details using [Logx].<br><br>
* [Logx]로 권한 거부 상세를 로깅합니다.<br>
*
* @param requestId Request identifier for the log entry.<br><br>
* 로그에 포함될 요청 식별자입니다.<br>
* @param permission Permission string being logged.<br><br>
* 로깅할 권한 문자열입니다.<br>
* @param result Permission decision type to log.<br><br>
* 로깅할 권한 결정 타입입니다.<br>
*/
private fun logPermissionResult(
requestId: String,
permission: String,
result: PermissionDecisionType,
) {
val type = classifier.classify(permission)
val deniedType = result.toDeniedTypeOrNull()?.name ?: "GRANTED"
Logx.d("$LOG_TAG: requestId=$requestId, permission=$permission, type=$type, deniedType=$deniedType")
}
/**
* Maps an internal decision type to an external denied type.<br><br>
* 내부 결정 타입을 외부 거부 타입으로 매핑합니다.<br>
*
* @return Return value: denied type or null when granted. Log behavior: none.<br><br>
* 반환값: 거부 타입 또는 승인 시 null. 로그 동작: 없음.<br>
*/
private fun PermissionDecisionType.toDeniedTypeOrNull(): PermissionDeniedType? = when (this) {
PermissionDecisionType.GRANTED -> null
PermissionDecisionType.DENIED -> PermissionDeniedType.DENIED
PermissionDecisionType.PERMANENTLY_DENIED -> PermissionDeniedType.PERMANENTLY_DENIED
PermissionDecisionType.MANIFEST_UNDECLARED -> PermissionDeniedType.MANIFEST_UNDECLARED
PermissionDecisionType.EMPTY_REQUEST -> PermissionDeniedType.EMPTY_REQUEST
PermissionDecisionType.NOT_SUPPORTED -> PermissionDeniedType.NOT_SUPPORTED
PermissionDecisionType.FAILED_TO_LAUNCH_SETTINGS -> PermissionDeniedType.FAILED_TO_LAUNCH_SETTINGS
PermissionDecisionType.LIFECYCLE_NOT_READY -> PermissionDeniedType.LIFECYCLE_NOT_READY
}
}