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 }