Coverage Summary for Class: ApplicationStateMonitor (dev.shreyaspatil.permissionFlow.internal)
| Class | Method, % | Branch, % | Line, % | Instruction, % |
|---|---|---|---|---|
| ApplicationStateMonitor | 100% (6/6) | 83.3% (5/6) | 100% (11/11) | 100% (52/52) |
| ApplicationStateMonitor$activityForegroundEvents$1 | 100% (2/2) | 100% (6/6) | 100% (37/37) | |
| ApplicationStateMonitor$activityForegroundEvents$1$callback$1 | 90% (9/10) | 92.3% (24/26) | 95.2% (20/21) | 97.5% (119/122) |
| Total | 94.4% (17/18) | 90.6% (29/32) | 97.4% (37/38) | 98.6% (208/211) |
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.internal 17 18 import android.app.Activity 19 import android.app.Application 20 import android.content.pm.PackageManager 21 import android.os.Build 22 import android.os.Bundle 23 import androidx.annotation.RequiresApi 24 import androidx.annotation.VisibleForTesting 25 import androidx.core.app.ActivityCompat 26 import androidx.core.content.ContextCompat 27 import dev.shreyaspatil.permissionFlow.PermissionState 28 import java.lang.ref.WeakReference 29 import kotlinx.coroutines.channels.awaitClose 30 import kotlinx.coroutines.flow.callbackFlow 31 32 /** 33 * Monitors the state of the application and provides information about the info and state of 34 * application. 35 */ 36 internal class ApplicationStateMonitor(private val application: Application) { 37 private var currentActivity: WeakReference<Activity>? = null 38 39 /** Returns the current state of the permission. */ 40 fun getPermissionState(permission: String): PermissionState { 41 val isGranted = isPermissionGranted(permission) 42 val isRationaleRequired = shouldShowPermissionRationale(permission) 43 return PermissionState(permission, isGranted, isRationaleRequired) 44 } 45 46 /** Returns whether the permission should show rationale or not. */ 47 private fun shouldShowPermissionRationale(permission: String): Boolean? { 48 val activity = currentActivity?.get() ?: return null 49 return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission) 50 } 51 52 /** Returns whether the permission is granted or not. */ 53 private fun isPermissionGranted(permission: String): Boolean { 54 return ContextCompat.checkSelfPermission( 55 application, 56 permission, 57 ) == PackageManager.PERMISSION_GRANTED 58 } 59 60 /** 61 * A flow which gives callback whenever any activity is started withing application (without 62 * configuration change) or any activity is resumed after being in multi-window or 63 * picture-in-picture mode. 64 */ 65 val activityForegroundEvents 66 get() = callbackFlow { 67 val callback = 68 object : Application.ActivityLifecycleCallbacks { 69 private var isActivityChangingConfigurations: Boolean? = null 70 private var wasInMultiWindowMode: Boolean? = null 71 private var wasInPictureInPictureMode: Boolean? = null 72 73 override fun onActivityPreCreated( 74 activity: Activity, 75 savedInstanceState: Bundle?, 76 ) { 77 currentActivity = WeakReference(activity) 78 } 79 80 override fun onActivityCreated( 81 activity: Activity, 82 savedInstanceState: Bundle?, 83 ) { 84 if (currentActivity?.get() != activity) { 85 currentActivity = WeakReference(activity) 86 } 87 } 88 89 /** 90 * Whenever activity receives onStart() lifecycle callback, emit foreground 91 * event only when activity hasn't changed configurations. 92 */ 93 override fun onActivityStarted(activity: Activity) { 94 if (isActivityChangingConfigurations == false) { 95 trySend(Unit) 96 } 97 } 98 99 override fun onActivityStopped(activity: Activity) { 100 isActivityChangingConfigurations = activity.isChangingConfigurations 101 } 102 103 /** 104 * Whenever application is resized after being in in PiP or multi-window mode, 105 * or exits from these modes, onResumed() lifecycle callback is triggered. 106 * 107 * Here we assume that user has changed permission from app settings after being 108 * in PiP or multi-window mode. So whenever these modes are exited, emit 109 * foreground event. 110 */ 111 override fun onActivityResumed(activity: Activity) { 112 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 113 if (isActivityResumedAfterMultiWindowOrPiPMode(activity)) { 114 trySend(Unit) 115 } 116 wasInMultiWindowMode = activity.isInMultiWindowMode 117 wasInPictureInPictureMode = activity.isInPictureInPictureMode 118 } 119 } 120 121 /** 122 * Whenever application is launched in PiP or multi-window mode, onPaused() 123 * lifecycle callback is triggered. 124 */ 125 override fun onActivityPaused(activity: Activity) { 126 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 127 wasInMultiWindowMode = activity.isInMultiWindowMode 128 wasInPictureInPictureMode = activity.isInPictureInPictureMode 129 } 130 } 131 132 override fun onActivitySaveInstanceState( 133 activity: Activity, 134 outState: Bundle, 135 ) {} 136 137 override fun onActivityDestroyed(activity: Activity) { 138 if (activity == currentActivity?.get()) { 139 currentActivity?.clear() 140 } 141 } 142 143 /** 144 * Returns whether [activity] was previously in multi-window mode or PiP mode. 145 */ 146 @RequiresApi(Build.VERSION_CODES.N) 147 private fun isActivityResumedAfterMultiWindowOrPiPMode(activity: Activity) = 148 (wasInMultiWindowMode == true && !activity.isInMultiWindowMode) || 149 (wasInPictureInPictureMode == true && 150 !activity.isInPictureInPictureMode) 151 } 152 153 application.registerActivityLifecycleCallbacks(callback) 154 155 awaitClose { 156 // Cleanup 157 application.unregisterActivityLifecycleCallbacks(callback) 158 } 159 } 160 161 @VisibleForTesting fun getCurrentActivityReference() = currentActivity 162 }