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