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)
|
|
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)
|
100%
(44/44)
|
98.3%
(294/299)
|
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 }