1 /*
2  * 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 androidx.activity.compose.rememberLauncherForActivityResult
22 import androidx.activity.result.ActivityResultLauncher
23 import androidx.activity.result.contract.ActivityResultContracts
24 import androidx.compose.runtime.Composable
25 import androidx.compose.runtime.DisposableEffect
26 import androidx.compose.runtime.Stable
27 import androidx.compose.runtime.getValue
28 import androidx.compose.runtime.mutableStateOf
29 import androidx.compose.runtime.remember
30 import androidx.compose.runtime.setValue
31 import androidx.compose.ui.platform.LocalContext
32 
33 /**
34  * Creates a [MutablePermissionState] that is remembered across compositions.
35  *
36  * It's recommended that apps exercise the permissions workflow as described in the
37  * [documentation](https://developer.android.com/training/permissions/requesting#workflow_for_requesting_permissions).
38  *
39  * @param permission the permission to control and observe.
40  * @param onPermissionResult will be called with whether or not the user granted the permission
41  *  after [PermissionState.launchPermissionRequest] is called.
42  */
43 @ExperimentalPermissionsApi
44 @Composable
rememberMutablePermissionStatenull45 internal fun rememberMutablePermissionState(
46     permission: String,
47     onPermissionResult: (Boolean) -> Unit = {}
48 ): MutablePermissionState {
49     val context = LocalContext.current
<lambda>null50     val permissionState = remember(permission) {
51         MutablePermissionState(permission, context, context.findActivity())
52     }
53 
54     // Refresh the permission status when the lifecycle is resumed
55     PermissionLifecycleCheckerEffect(permissionState)
56 
57     // Remember RequestPermission launcher and assign it to permissionState
<lambda>null58     val launcher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {
59         permissionState.refreshPermissionStatus()
60         onPermissionResult(it)
61     }
<lambda>null62     DisposableEffect(permissionState, launcher) {
63         permissionState.launcher = launcher
64         onDispose {
65             permissionState.launcher = null
66         }
67     }
68 
69     return permissionState
70 }
71 
72 /**
73  * A mutable state object that can be used to control and observe permission status changes.
74  *
75  * In most cases, this will be created via [rememberMutablePermissionState].
76  *
77  * @param permission the permission to control and observe.
78  * @param context to check the status of the [permission].
79  * @param activity to check if the user should be presented with a rationale for [permission].
80  */
81 @ExperimentalPermissionsApi
82 @Stable
83 internal class MutablePermissionState(
84     override val permission: String,
85     private val context: Context,
86     private val activity: Activity
87 ) : PermissionState {
88 
89     override var status: PermissionStatus by mutableStateOf(getPermissionStatus())
90 
launchPermissionRequestnull91     override fun launchPermissionRequest() {
92         launcher?.launch(
93             permission
94         ) ?: throw IllegalStateException("ActivityResultLauncher cannot be null")
95     }
96 
97     internal var launcher: ActivityResultLauncher<String>? = null
98 
refreshPermissionStatusnull99     internal fun refreshPermissionStatus() {
100         status = getPermissionStatus()
101     }
102 
getPermissionStatusnull103     private fun getPermissionStatus(): PermissionStatus {
104         val hasPermission = context.checkPermission(permission)
105         return if (hasPermission) {
106             PermissionStatus.Granted
107         } else {
108             PermissionStatus.Denied(activity.shouldShowRationale(permission))
109         }
110     }
111 }
112