1 /*
<lambda>null2 * Copyright 2021 The Android Open Source Project
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 * https://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
17 package com.google.accompanist.permissions
18
19 import android.app.Activity
20 import android.content.Context
21 import android.content.ContextWrapper
22 import android.content.pm.PackageManager
23 import androidx.compose.runtime.Composable
24 import androidx.compose.runtime.DisposableEffect
25 import androidx.compose.runtime.Stable
26 import androidx.compose.runtime.remember
27 import androidx.compose.ui.platform.LocalLifecycleOwner
28 import androidx.core.app.ActivityCompat
29 import androidx.core.content.ContextCompat
30 import androidx.lifecycle.Lifecycle
31 import androidx.lifecycle.LifecycleEventObserver
32
33 @RequiresOptIn(message = "Accompanist Permissions is experimental. The API may be changed in the future.")
34 @Retention(AnnotationRetention.BINARY)
35 public annotation class ExperimentalPermissionsApi
36
37 /**
38 * Model of the status of a permission. It can be granted or denied.
39 * If denied, the user might need to be presented with a rationale.
40 */
41 @ExperimentalPermissionsApi
42 @Stable
43 public sealed interface PermissionStatus {
44 public object Granted : PermissionStatus
45 public data class Denied(
46 val shouldShowRationale: Boolean
47 ) : PermissionStatus
48 }
49
50 /**
51 * `true` if the permission is granted.
52 */
53 @ExperimentalPermissionsApi
54 public val PermissionStatus.isGranted: Boolean
55 get() = this == PermissionStatus.Granted
56
57 /**
58 * `true` if a rationale should be presented to the user.
59 */
60 @ExperimentalPermissionsApi
61 public val PermissionStatus.shouldShowRationale: Boolean
62 get() = when (this) {
63 PermissionStatus.Granted -> false
64 is PermissionStatus.Denied -> shouldShowRationale
65 }
66
67 /**
68 * Effect that updates the `hasPermission` state of a revoked [MutablePermissionState] permission
69 * when the lifecycle gets called with [lifecycleEvent].
70 */
71 @ExperimentalPermissionsApi
72 @Composable
PermissionLifecycleCheckerEffectnull73 internal fun PermissionLifecycleCheckerEffect(
74 permissionState: MutablePermissionState,
75 lifecycleEvent: Lifecycle.Event = Lifecycle.Event.ON_RESUME
76 ) {
77 // Check if the permission was granted when the lifecycle is resumed.
78 // The user might've gone to the Settings screen and granted the permission.
79 val permissionCheckerObserver = remember(permissionState) {
80 LifecycleEventObserver { _, event ->
81 if (event == lifecycleEvent) {
82 // If the permission is revoked, check again.
83 // We don't check if the permission was denied as that triggers a process restart.
84 if (permissionState.status != PermissionStatus.Granted) {
85 permissionState.refreshPermissionStatus()
86 }
87 }
88 }
89 }
90 val lifecycle = LocalLifecycleOwner.current.lifecycle
91 DisposableEffect(lifecycle, permissionCheckerObserver) {
92 lifecycle.addObserver(permissionCheckerObserver)
93 onDispose { lifecycle.removeObserver(permissionCheckerObserver) }
94 }
95 }
96
97 /**
98 * Effect that updates the `hasPermission` state of a list of permissions
99 * when the lifecycle gets called with [lifecycleEvent] and the permission is revoked.
100 */
101 @ExperimentalPermissionsApi
102 @Composable
PermissionsLifecycleCheckerEffectnull103 internal fun PermissionsLifecycleCheckerEffect(
104 permissions: List<MutablePermissionState>,
105 lifecycleEvent: Lifecycle.Event = Lifecycle.Event.ON_RESUME
106 ) {
107 // Check if the permission was granted when the lifecycle is resumed.
108 // The user might've gone to the Settings screen and granted the permission.
109 val permissionsCheckerObserver = remember(permissions) {
110 LifecycleEventObserver { _, event ->
111 if (event == lifecycleEvent) {
112 for (permission in permissions) {
113 // If the permission is revoked, check again. We don't check if the permission
114 // was denied as that triggers a process restart.
115 if (permission.status != PermissionStatus.Granted) {
116 permission.refreshPermissionStatus()
117 }
118 }
119 }
120 }
121 }
122 val lifecycle = LocalLifecycleOwner.current.lifecycle
123 DisposableEffect(lifecycle, permissionsCheckerObserver) {
124 lifecycle.addObserver(permissionsCheckerObserver)
125 onDispose { lifecycle.removeObserver(permissionsCheckerObserver) }
126 }
127 }
128
129 /**
130 * Find the closest Activity in a given Context.
131 */
findActivitynull132 internal fun Context.findActivity(): Activity {
133 var context = this
134 while (context is ContextWrapper) {
135 if (context is Activity) return context
136 context = context.baseContext
137 }
138 throw IllegalStateException("Permissions should be called in the context of an Activity")
139 }
140
checkPermissionnull141 internal fun Context.checkPermission(permission: String): Boolean {
142 return ContextCompat.checkSelfPermission(this, permission) ==
143 PackageManager.PERMISSION_GRANTED
144 }
145
shouldShowRationalenull146 internal fun Activity.shouldShowRationale(permission: String): Boolean {
147 return ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
148 }
149