5
5
package dev.icerock.moko.permissions
6
6
7
7
import android.Manifest
8
+ import android.app.Activity
8
9
import android.content.Context
10
+ import android.content.ContextWrapper
9
11
import android.content.Intent
10
12
import android.content.pm.PackageManager
11
13
import android.net.Uri
12
14
import android.os.Build
13
15
import android.provider.Settings
16
+ import androidx.activity.result.ActivityResultLauncher
17
+ import androidx.activity.result.ActivityResultRegistryOwner
18
+ import androidx.activity.result.contract.ActivityResultContracts
19
+ import androidx.core.app.ActivityCompat
20
+ import androidx.activity.ComponentActivity
14
21
import androidx.core.app.NotificationManagerCompat
15
22
import androidx.core.content.ContextCompat
16
- import androidx.fragment.app.Fragment
17
- import androidx.fragment.app.FragmentManager
18
23
import androidx.lifecycle.Lifecycle
19
24
import androidx.lifecycle.LifecycleEventObserver
20
25
import androidx.lifecycle.LifecycleOwner
21
26
import kotlinx.coroutines.flow.MutableStateFlow
22
- import kotlinx.coroutines.flow.filterNotNull
23
- import kotlinx.coroutines.flow.first
24
27
import kotlinx.coroutines.sync.Mutex
25
28
import kotlinx.coroutines.sync.withLock
26
- import kotlinx.coroutines.withTimeoutOrNull
29
+ import java.util.UUID
27
30
import kotlin.coroutines.suspendCoroutine
28
31
29
32
@Suppress(" TooManyFunctions" )
30
33
class PermissionsControllerImpl (
31
- private val resolverFragmentTag : String = " PermissionsControllerResolver" ,
32
34
private val applicationContext : Context ,
33
35
) : PermissionsController {
34
- private val fragmentManagerHolder = MutableStateFlow <FragmentManager ?>(null )
36
+ private val activityHolder = MutableStateFlow <Activity ?>(null )
37
+
35
38
private val mutex: Mutex = Mutex ()
36
39
37
- override fun bind (lifecycle : Lifecycle , fragmentManager : FragmentManager ) {
38
- this .fragmentManagerHolder.value = fragmentManager
40
+ private var launcher: ActivityResultLauncher <Array <String >>? = null
41
+
42
+ private var permissionCallback: PermissionCallback ? = null
43
+
44
+ override fun bind (activity : ComponentActivity ) {
45
+ this .activityHolder.value = activity
46
+ val activityResultRegistryOwner = activity as ActivityResultRegistryOwner
47
+
48
+ val key = UUID .randomUUID().toString()
49
+
50
+ launcher = activityResultRegistryOwner.activityResultRegistry.register(
51
+ key,
52
+ ActivityResultContracts .RequestMultiplePermissions ()
53
+ ) { permissions ->
54
+ val isCancelled = permissions.isEmpty()
55
+
56
+ val permissionCallback = permissionCallback ? : return @register
57
+
58
+ if (isCancelled) {
59
+ permissionCallback.callback.invoke(
60
+ Result .failure(RequestCanceledException (permissionCallback.permission))
61
+ )
62
+ return @register
63
+ }
64
+
65
+ val success = permissions.values.all { it }
66
+
67
+ if (success) {
68
+ permissionCallback.callback.invoke(Result .success(Unit ))
69
+ } else {
70
+ if (shouldShowRequestPermissionRationale(permissions.keys.first())) {
71
+ permissionCallback.callback.invoke(
72
+ Result .failure(DeniedException (permissionCallback.permission))
73
+ )
74
+ } else {
75
+ permissionCallback.callback.invoke(
76
+ Result .failure(DeniedAlwaysException (permissionCallback.permission))
77
+ )
78
+ }
79
+ }
80
+ }
39
81
40
82
val observer = object : LifecycleEventObserver {
41
83
override fun onStateChanged (source : LifecycleOwner , event : Lifecycle .Event ) {
42
84
if (event == Lifecycle .Event .ON_DESTROY ) {
43
- this @PermissionsControllerImpl.fragmentManagerHolder .value = null
85
+ this @PermissionsControllerImpl.activityHolder .value = null
44
86
source.lifecycle.removeObserver(this )
45
87
}
46
88
}
47
89
}
48
- lifecycle.addObserver(observer)
90
+ activity. lifecycle.addObserver(observer)
49
91
}
50
92
51
93
override suspend fun providePermission (permission : Permission ) {
52
94
mutex.withLock {
53
- val fragmentManager: FragmentManager = awaitFragmentManager()
54
- val resolverFragment: ResolverFragment = getOrCreateResolverFragment(fragmentManager)
55
-
56
95
val platformPermission = permission.toPlatformPermission()
57
96
suspendCoroutine { continuation ->
58
- resolverFragment. requestPermission(
97
+ requestPermission(
59
98
permission,
60
99
platformPermission
61
100
) { continuation.resumeWith(it) }
62
101
}
63
102
}
64
103
}
65
104
105
+ private fun requestPermission (
106
+ permission : Permission ,
107
+ permissions : List <String >,
108
+ callback : (Result <Unit >) -> Unit
109
+ ) {
110
+ permissionCallback = PermissionCallback (permission, callback)
111
+ launcher?.launch(permissions.toTypedArray())
112
+ }
113
+
66
114
override suspend fun isPermissionGranted (permission : Permission ): Boolean {
67
115
return getPermissionState(permission) == PermissionState .Granted
68
116
}
@@ -87,16 +135,27 @@ class PermissionsControllerImpl(
87
135
val isAllGranted: Boolean = status.all { it == PackageManager .PERMISSION_GRANTED }
88
136
if (isAllGranted) return PermissionState .Granted
89
137
90
- val fragmentManager: FragmentManager = awaitFragmentManager()
91
- val resolverFragment: ResolverFragment = getOrCreateResolverFragment(fragmentManager)
92
-
93
138
val isAllRequestRationale: Boolean = permissions.all {
94
- ! resolverFragment. shouldShowRequestPermissionRationale(it)
139
+ shouldShowRequestPermissionRationale(it). not ( )
95
140
}
96
141
return if (isAllRequestRationale) PermissionState .NotDetermined
97
142
else PermissionState .Denied
98
143
}
99
144
145
+ private fun shouldShowRequestPermissionRationale (permission : String ): Boolean {
146
+ val activity: Activity = checkNotNull(this .activityHolder.value) {
147
+ " ${this .activityHolder.value} activity is null, `bind` function was never called," +
148
+ " consider calling permissionsController.bind(activity)" +
149
+ " or BindEffect(permissionsController) in the composable function," +
150
+ " check the documentation for more info: " +
151
+ " https://github.com/icerockdev/moko-permissions/blob/master/README.md"
152
+ }
153
+ return ActivityCompat .shouldShowRequestPermissionRationale(
154
+ activity,
155
+ permission
156
+ )
157
+ }
158
+
100
159
override fun openAppSettings () {
101
160
val intent = Intent ().apply {
102
161
action = Settings .ACTION_APPLICATION_DETAILS_SETTINGS
@@ -106,35 +165,6 @@ class PermissionsControllerImpl(
106
165
applicationContext.startActivity(intent)
107
166
}
108
167
109
- private suspend fun awaitFragmentManager (): FragmentManager {
110
- val fragmentManager: FragmentManager ? = fragmentManagerHolder.value
111
- if (fragmentManager != null ) return fragmentManager
112
-
113
- return withTimeoutOrNull(AWAIT_FRAGMENT_MANAGER_TIMEOUT_DURATION_MS ) {
114
- fragmentManagerHolder.filterNotNull().first()
115
- } ? : error(
116
- " fragmentManager is null, `bind` function was never called," +
117
- " consider calling permissionsController.bind(lifecycle, fragmentManager)" +
118
- " or BindEffect(permissionsController) in the composable function," +
119
- " check the documentation for more info: " +
120
- " https://github.com/icerockdev/moko-permissions/blob/master/README.md"
121
- )
122
- }
123
-
124
- private fun getOrCreateResolverFragment (fragmentManager : FragmentManager ): ResolverFragment {
125
- val currentFragment: Fragment ? = fragmentManager.findFragmentByTag(resolverFragmentTag)
126
- return if (currentFragment != null ) {
127
- currentFragment as ResolverFragment
128
- } else {
129
- ResolverFragment ().also { fragment ->
130
- fragmentManager
131
- .beginTransaction()
132
- .add(fragment, resolverFragmentTag)
133
- .commit()
134
- }
135
- }
136
- }
137
-
138
168
@Suppress(" CyclomaticComplexMethod" )
139
169
private fun Permission.toPlatformPermission (): List <String > {
140
170
return when (this ) {
@@ -256,6 +286,10 @@ class PermissionsControllerImpl(
256
286
private companion object {
257
287
val VERSIONS_WITHOUT_NOTIFICATION_PERMISSION =
258
288
Build .VERSION_CODES .KITKAT until Build .VERSION_CODES .TIRAMISU
259
- private const val AWAIT_FRAGMENT_MANAGER_TIMEOUT_DURATION_MS = 2000L
260
289
}
261
290
}
291
+
292
+ private class PermissionCallback (
293
+ val permission : Permission ,
294
+ val callback : (Result <Unit >) -> Unit
295
+ )
0 commit comments