aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/ca/allanwang/kau/permissions/PermissionManager.kt
blob: 96adfa332b236620054cbc4404e6e5c5df609654 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/*
 * Copyright 2018 Allan Wang
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ca.allanwang.kau.permissions

import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import ca.allanwang.kau.kotlin.kauRemoveIf
import ca.allanwang.kau.kotlin.lazyContext
import ca.allanwang.kau.logging.KL
import ca.allanwang.kau.utils.KauException
import ca.allanwang.kau.utils.buildIsMarshmallowAndUp
import ca.allanwang.kau.utils.hasPermission
import ca.allanwang.kau.utils.toast
import java.lang.ref.WeakReference

/**
 * Created by Allan Wang on 2017-07-03.
 *
 * Permission manager that is decoupled from activities.
 * Keeps track of pending requests, and warns about invalid requests.
 */
internal object PermissionManager {

    private val pendingResults = mutableListOf<WeakReference<PermissionResult>>()

    /**
     * Retrieve permissions requested in our manifest
     */
    private val manifestPermission = lazyContext<Set<String>> {
        try {
            it.packageManager.getPackageInfo(
                it.packageName,
                PackageManager.GET_PERMISSIONS
            )?.requestedPermissions?.toSet()
                ?: emptySet()
        } catch (e: Exception) {
            emptySet()
        }
    }

    /**
     * Registers a new permission request.
     * It is expected that the callback will be called eventually, unless the parent activity is destroyed.
     */
    operator fun invoke(
        context: Context,
        permissions: Array<out String>,
        callback: (granted: Boolean, deniedPerm: String?) -> Unit
    ) {
        KL.d { "Permission manager for: ${permissions.contentToString()}" }
        if (!buildIsMarshmallowAndUp) return callback(true, null)
        val missingPermissions = permissions.filter { !context.hasPermission(it) }
        if (missingPermissions.isEmpty()) return callback(true, null)
        pendingResults.add(WeakReference(PermissionResult(permissions, callback = callback)))
        if (pendingResults.size == 1) {
            requestPermissions(context, missingPermissions.toTypedArray())
        } else {
            KL.d { "Request is postponed since another one is still in progress; did you remember to override onRequestPermissionsResult?" }
        }
    }

    /**
     * Checks that the listed permissions can be requested, and submits the request to the provided activity
     */
    private fun requestPermissions(context: Context, permissions: Array<out String>) {
        permissions.forEach {
            if (!manifestPermission(context).contains(it)) {
                KL.e { "Requested permission $it is not stated in the manifest" }
                context.toast("$it is not in the manifest")
                //we'll let the request pass through so it can be denied and so the callback can be triggered
            }
        }
        val activity = (context as? Activity)
            ?: throw KauException("Context is not an instance of an activity; cannot request permissions")
        KL.i { "Requesting permissions ${permissions.contentToString()}" }
        ActivityCompat.requestPermissions(activity, permissions, 1)
    }

    /**
     * Handles permission result by allowing accepted permissions for all pending requests.
     * Also cleans up destroyed or completed pending requests.
     */
    fun onRequestPermissionsResult(context: Context, permissions: Array<out String>, grantResults: IntArray) {
        KL.i { "On permission result: pending ${pendingResults.size}" }
        val count = Math.min(permissions.size, grantResults.size)
        pendingResults.kauRemoveIf {
            val action = it.get()
            action == null || (0 until count).any { i -> action.onResult(permissions[i], grantResults[i]) }
        }
        val action = pendingResults.asSequence().map { it.get() }.firstOrNull { it != null }
        if (action == null) { // actions have been unlinked from their weak references
            pendingResults.clear()
            return
        }
        requestPermissions(context, action.permissions.toTypedArray())
    }
}