






























@@ -17,81 +17,149 @@ import androidx.lifecycle.Lifecycle
1717import androidx.lifecycle.LifecycleEventObserver
1818import kotlinx.coroutines.CompletableDeferred
1919import kotlinx.coroutines.Dispatchers
20+import kotlinx.coroutines.TimeoutCancellationException
2021import kotlinx.coroutines.suspendCancellableCoroutine
2122import kotlinx.coroutines.sync.Mutex
2223import kotlinx.coroutines.sync.withLock
2324import kotlinx.coroutines.withContext
25+import kotlinx.coroutines.withTimeout
2426import java.util.concurrent.atomic.AtomicBoolean
2527import kotlin.coroutines.resume
262827-class PermissionRequester(
29+class PermissionRequester internal constructor(
2830private val activity: ComponentActivity,
31+launcherFactory: ((Map<String, Boolean>) -> Unit) -> ActivityResultLauncher<Array<String>>,
2932) {
33+private data class PendingPermissionRequest(
34+val deferred: CompletableDeferred<Map<String, Boolean>>,
35+var timedOut: Boolean = false,
36+ )
37+38+private class PermissionRequestSlot(
39+val launcher: ActivityResultLauncher<Array<String>>,
40+var request: PendingPermissionRequest? = null,
41+ )
42+43+constructor(activity: ComponentActivity) : this(
44+ activity = activity,
45+ launcherFactory = { callback ->
46+ activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions(), callback)
47+ },
48+ )
49+3050private val mutex = Mutex()
31-private var pending: CompletableDeferred<Map<String, Boolean>>? = null
51+private val requestSlotsLock = Any()
3252private val mainHandler = Handler(Looper.getMainLooper())
33-34-private val launcher: ActivityResultLauncher<Array<String>> =
35- activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
36-val p = pending
37- pending = null
38- p?.complete(result)
39- }
53+private val launchers = List(4) { createPermissionRequestSlot(launcherFactory) }
40544155suspend fun requestIfMissing(
4256permissions: List<String>,
4357timeoutMs: Long = 20_000,
44- ): Map<String, Boolean> =
45- mutex.withLock {
46-val missing =
47- permissions.filter { perm ->
48-ContextCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED
58+ ): Map<String, Boolean> {
59+return mutex.withLock {
60+while (true) {
61+val missing =
62+ permissions.filter { perm ->
63+ContextCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED
64+ }
65+if (missing.isEmpty()) {
66+return permissions.associateWith { true }
4967 }
50-if (missing.isEmpty()) {
51-return permissions.associateWith { true }
52- }
536854-val needsRationale =
55- missing.any { ActivityCompat.shouldShowRequestPermissionRationale(activity, it) }
56-if (needsRationale) {
57-val proceed = showRationaleDialog(missing)
58-if (!proceed) {
59-return permissions.associateWith { perm ->
60-ContextCompat.checkSelfPermission(activity, perm) == PackageManager.PERMISSION_GRANTED
69+val needsRationale =
70+ missing.any { ActivityCompat.shouldShowRequestPermissionRationale(activity, it) }
71+if (needsRationale) {
72+val proceed = showRationaleDialog(missing)
73+if (!proceed) {
74+return permissions.associateWith { perm ->
75+ContextCompat.checkSelfPermission(activity, perm) == PackageManager.PERMISSION_GRANTED
76+ }
6177 }
6278 }
63- }
64-65-val deferred = CompletableDeferred<Map<String, Boolean>>()
66- pending = deferred
67- withContext(Dispatchers.Main) {
68- launcher.launch(missing.toTypedArray())
69- }
707971-val result =
72- withContext(Dispatchers.Default) {
73- kotlinx.coroutines.withTimeout(timeoutMs) { deferred.await() }
80+val deferred = CompletableDeferred<Map<String, Boolean>>()
81+val request = PendingPermissionRequest(deferred)
82+val slot = reservePermissionRequestSlot(request)
83+try {
84+ withContext(Dispatchers.Main) {
85+ slot.launcher.launch(missing.toTypedArray())
86+ }
87+ } catch (err: Throwable) {
88+ clearPermissionRequestSlot(slot, request)
89+throw err
7490 }
759176-// Merge: if something was already granted, treat it as granted even if launcher omitted it.
77-val merged =
78- permissions.associateWith { perm ->
79-val nowGranted =
80-ContextCompat.checkSelfPermission(activity, perm) == PackageManager.PERMISSION_GRANTED
81- result[perm] == true || nowGranted
82- }
92+val result =
93+try {
94+ withTimeout(timeoutMs) { deferred.await() }
95+ } catch (err: TimeoutCancellationException) {
96+ request.timedOut = true
97+throw err
98+ }
99+100+val merged =
101+ permissions.associateWith { perm ->
102+val nowGranted =
103+ContextCompat.checkSelfPermission(activity, perm) == PackageManager.PERMISSION_GRANTED
104+ result[perm] == true || nowGranted
105+ }
8310684-val denied =
85- merged.filterValues { !it }.keys.filter {
86-!ActivityCompat.shouldShowRequestPermissionRationale(activity, it)
107+val denied =
108+ merged.filterValues { !it }.keys.filter {
109+!ActivityCompat.shouldShowRequestPermissionRationale(activity, it)
110+ }
111+if (denied.isNotEmpty()) {
112+ showSettingsDialog(denied)
87113 }
88- if (denied.isNotEmpty()) {
89-showSettingsDialog(denied)
114+115+return merged
90116 }
117+ error("unreachable")
118+ }
119+ }
120+121+private fun createPermissionRequestSlot(
122+launcherFactory: ((Map<String, Boolean>) -> Unit) -> ActivityResultLauncher<Array<String>>,
123+ ): PermissionRequestSlot {
124+var slot: PermissionRequestSlot? = null
125+val launcher = launcherFactory { result -> completePermissionRequest(checkNotNull(slot), result) }
126+val created = PermissionRequestSlot(launcher)
127+ slot = created
128+return created
129+ }
9113092-return merged
131+private fun reservePermissionRequestSlot(request: PendingPermissionRequest): PermissionRequestSlot =
132+ synchronized(requestSlotsLock) {
133+val slot = launchers.firstOrNull { it.request == null } ?: error("permission request launcher busy")
134+ slot.request = request
135+ slot
93136 }
94137138+private fun completePermissionRequest(
139+slot: PermissionRequestSlot,
140+result: Map<String, Boolean>,
141+ ) {
142+val request =
143+ synchronized(requestSlotsLock) {
144+ slot.request.also {
145+ slot.request = null
146+ }
147+ } ?: return
148+if (request.timedOut) return
149+ request.deferred.complete(result)
150+ }
151+152+private fun clearPermissionRequestSlot(
153+slot: PermissionRequestSlot,
154+request: PendingPermissionRequest,
155+ ) {
156+ synchronized(requestSlotsLock) {
157+if (slot.request === request) {
158+ slot.request = null
159+ }
160+ }
161+ }
162+95163private suspend fun showRationaleDialog(permissions: List<String>): Boolean =
96164 withContext(Dispatchers.Main) {
97165if (activity.isFinishing || activity.isDestroyed) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。