Coverage Summary for Class: PermissionWatchmen (dev.shreyaspatil.permissionFlow.watchmen)
| Class | Method, % | Branch, % | Line, % | Instruction, % |
|---|---|---|---|---|
| PermissionWatchmen | 100% (10/10) | 88.9% (16/18) | 100% (31/31) | 97.8% (181/185) |
| PermissionWatchmen$notifyPermissionsChanged$1 | 100% (1/1) | 100% (3/3) | 100% (39/39) | |
| PermissionWatchmen$PermissionStateFlowDelegate | 100% (2/2) | 100% (4/4) | 100% (17/17) | |
| PermissionWatchmen$watchActivities$1 | 100% (1/1) | 100% (3/3) | 100% (16/16) | |
| PermissionWatchmen$watchMultipleState$$inlined$combineStates$1 | 0% (0/1) | |||
| PermissionWatchmen$watchMultipleState$$inlined$combineStates$2 | 0% (0/2) | |||
| PermissionWatchmen$watchMultipleState$$inlined$combineStates$2$2 | 0% (0/1) | 0% (0/1) | 0% (0/4) | |
| PermissionWatchmen$watchMultipleState$$inlined$combineStates$2$3 | 0% (0/1) | |||
| PermissionWatchmen$watchPermissionEvents$1 | 100% (1/1) | 100% (2/2) | 100% (28/28) | |
| PermissionWatchmen$watchPermissionEvents$1$1 | 100% (1/1) | 50% (1/2) | 100% (1/1) | 92.9% (13/14) |
| Total | 76.2% (16/21) | 85% (17/20) | 97.8% (44/45) | 97% (294/303) |
1 /** 2 * Copyright 2022 Shreyas Patil 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package dev.shreyaspatil.permissionFlow.watchmen 17 18 import dev.shreyaspatil.permissionFlow.MultiplePermissionState 19 import dev.shreyaspatil.permissionFlow.PermissionState 20 import dev.shreyaspatil.permissionFlow.internal.ApplicationStateMonitor 21 import dev.shreyaspatil.permissionFlow.utils.stateFlow.combineStates 22 import kotlinx.coroutines.CoroutineDispatcher 23 import kotlinx.coroutines.CoroutineName 24 import kotlinx.coroutines.CoroutineScope 25 import kotlinx.coroutines.Job 26 import kotlinx.coroutines.SupervisorJob 27 import kotlinx.coroutines.cancelChildren 28 import kotlinx.coroutines.flow.MutableSharedFlow 29 import kotlinx.coroutines.flow.MutableStateFlow 30 import kotlinx.coroutines.flow.StateFlow 31 import kotlinx.coroutines.flow.asStateFlow 32 import kotlinx.coroutines.flow.launchIn 33 import kotlinx.coroutines.flow.onEach 34 import kotlinx.coroutines.launch 35 import kotlinx.coroutines.yield 36 37 /** A watchmen which keeps watching state changes of permissions and events of permissions. */ 38 @Suppress("unused") 39 internal class PermissionWatchmen( 40 private val appStateMonitor: ApplicationStateMonitor, 41 dispatcher: CoroutineDispatcher, 42 ) { 43 private val watchmenScope = 44 CoroutineScope( 45 dispatcher + SupervisorJob() + CoroutineName("PermissionWatchmen"), 46 ) 47 48 private var watchEventsJob: Job? = null 49 private var watchActivityEventJob: Job? = null 50 51 /** A in-memory store for storing permission and its state holder i.e. [StateFlow] */ 52 private val permissionFlows = mutableMapOf<String, PermissionStateFlowDelegate>() 53 54 private val permissionEvents = MutableSharedFlow<PermissionState>() 55 56 fun watchState(permission: String): StateFlow<PermissionState> { 57 // Wakeup watchmen if sleeping 58 wakeUp() 59 return getOrCreatePermissionStateFlow(permission) 60 } 61 62 fun watchMultipleState(permissions: Array<String>): StateFlow<MultiplePermissionState> { 63 // Wakeup watchmen if sleeping 64 wakeUp() 65 66 val permissionStates = 67 permissions.distinct().map { getOrCreatePermissionStateFlow(it) }.toTypedArray() 68 69 return combineStates(*permissionStates) { MultiplePermissionState(it.toList()) } 70 } 71 72 fun notifyPermissionsChanged(permissions: Array<String>) { 73 watchmenScope.launch { 74 permissions.forEach { permission -> 75 permissionEvents.emit(appStateMonitor.getPermissionState(permission)) 76 } 77 } 78 } 79 80 @Synchronized 81 fun wakeUp() { 82 watchPermissionEvents() 83 watchActivities() 84 notifyAllPermissionsChanged() 85 } 86 87 @Synchronized 88 fun sleep() { 89 watchmenScope.coroutineContext.cancelChildren() 90 } 91 92 /** 93 * First finds for existing flow (if available) otherwise creates a new [MutableStateFlow] for 94 * [permission] and returns a read-only [StateFlow] for a [permission]. 95 */ 96 @Synchronized 97 private fun getOrCreatePermissionStateFlow(permission: String): StateFlow<PermissionState> { 98 return permissionFlows 99 .getOrPut(permission) { 100 PermissionStateFlowDelegate(appStateMonitor.getPermissionState(permission)) 101 } 102 .state 103 } 104 105 /** Watches for the permission events and updates appropriate state holders of permission */ 106 private fun watchPermissionEvents() { 107 if (watchEventsJob != null && watchEventsJob?.isActive == true) return 108 watchEventsJob = 109 watchmenScope.launch { 110 permissionEvents.collect { permissionFlows[it.permission]?.setState(it) } 111 } 112 } 113 114 /** 115 * Watches for activity foreground events (to detect whether user has changed permission by 116 * going in settings) and recalculates state of the permissions which are currently being 117 * observed. 118 */ 119 private fun watchActivities() { 120 if (watchActivityEventJob != null && watchActivityEventJob?.isActive == true) return 121 watchActivityEventJob = 122 appStateMonitor.activityForegroundEvents 123 .onEach { 124 // Since this is not priority task, we want to yield current thread for other 125 // tasks for the watchmen. 126 yield() 127 notifyAllPermissionsChanged() 128 } 129 .launchIn(watchmenScope) 130 } 131 132 private fun notifyAllPermissionsChanged() { 133 if (permissionFlows.isEmpty()) return 134 notifyPermissionsChanged(permissionFlows.keys.toTypedArray()) 135 } 136 137 /** A delegate for [MutableStateFlow] which creates flow for holding state of a permission. */ 138 private class PermissionStateFlowDelegate(initialState: PermissionState) { 139 140 private val _state = MutableStateFlow(initialState) 141 val state = _state.asStateFlow() 142 143 fun setState(newState: PermissionState) { 144 _state.value = newState 145 } 146 } 147 }