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 androidx.activity.compose.rememberLauncherForActivityResult
20 import androidx.activity.result.ActivityResultLauncher
21 import androidx.activity.result.contract.ActivityResultContracts
22 import androidx.compose.runtime.Composable
23 import androidx.compose.runtime.DisposableEffect
24 import androidx.compose.runtime.Stable
25 import androidx.compose.runtime.derivedStateOf
26 import androidx.compose.runtime.getValue
27 import androidx.compose.runtime.key
28 import androidx.compose.runtime.remember
29 import androidx.compose.ui.platform.LocalContext
30 
31 /**
32  * Creates a [MultiplePermissionsState] that is remembered across compositions.
33  *
34  * It's recommended that apps exercise the permissions workflow as described in the
35  * [documentation](https://developer.android.com/training/permissions/requesting#workflow_for_requesting_permissions).
36  *
37  * @param permissions the permissions to control and observe.
38  * @param onPermissionsResult will be called with whether or not the user granted the permissions
39  *  after [MultiplePermissionsState.launchMultiplePermissionRequest] is called.
40  */
41 @ExperimentalPermissionsApi
42 @Composable
43 internal fun rememberMutableMultiplePermissionsState(
44     permissions: List<String>,
45     onPermissionsResult: (Map<String, Boolean>) -> Unit = {}
46 ): MultiplePermissionsState {
47     // Create mutable permissions that can be requested individually
48     val mutablePermissions = rememberMutablePermissionsState(permissions)
49     // Refresh permissions when the lifecycle is resumed.
50     PermissionsLifecycleCheckerEffect(mutablePermissions)
51 
<lambda>null52     val multiplePermissionsState = remember(permissions) {
53         MutableMultiplePermissionsState(mutablePermissions)
54     }
55 
56     // Remember RequestMultiplePermissions launcher and assign it to multiplePermissionsState
57     val launcher = rememberLauncherForActivityResult(
58         ActivityResultContracts.RequestMultiplePermissions()
permissionsResultnull59     ) { permissionsResult ->
60         multiplePermissionsState.updatePermissionsStatus(permissionsResult)
61         onPermissionsResult(permissionsResult)
62     }
<lambda>null63     DisposableEffect(multiplePermissionsState, launcher) {
64         multiplePermissionsState.launcher = launcher
65         onDispose {
66             multiplePermissionsState.launcher = null
67         }
68     }
69 
70     return multiplePermissionsState
71 }
72 
73 @ExperimentalPermissionsApi
74 @Composable
rememberMutablePermissionsStatenull75 private fun rememberMutablePermissionsState(
76     permissions: List<String>
77 ): List<MutablePermissionState> {
78     // Create list of MutablePermissionState for each permission
79     val context = LocalContext.current
80     val activity = context.findActivity()
81     val mutablePermissions: List<MutablePermissionState> = remember(permissions) {
82         return@remember permissions.map { MutablePermissionState(it, context, activity) }
83     }
84     // Update each permission with its own launcher
85     for (permissionState in mutablePermissions) {
86         key(permissionState.permission) {
87             // Remember launcher and assign it to the permissionState
88             val launcher = rememberLauncherForActivityResult(
89                 ActivityResultContracts.RequestPermission()
90             ) {
91                 permissionState.refreshPermissionStatus()
92             }
93             DisposableEffect(launcher) {
94                 permissionState.launcher = launcher
95                 onDispose {
96                     permissionState.launcher = null
97                 }
98             }
99         }
100     }
101 
102     return mutablePermissions
103 }
104 
105 /**
106  * A state object that can be hoisted to control and observe multiple permission status changes.
107  *
108  * In most cases, this will be created via [rememberMutableMultiplePermissionsState].
109  *
110  * @param mutablePermissions list of mutable permissions to control and observe.
111  */
112 @ExperimentalPermissionsApi
113 @Stable
114 internal class MutableMultiplePermissionsState(
115     private val mutablePermissions: List<MutablePermissionState>
116 ) : MultiplePermissionsState {
117 
118     override val permissions: List<PermissionState> = mutablePermissions
119 
<lambda>null120     override val revokedPermissions: List<PermissionState> by derivedStateOf {
121         permissions.filter { it.status != PermissionStatus.Granted }
122     }
123 
<lambda>null124     override val allPermissionsGranted: Boolean by derivedStateOf {
125         permissions.all { it.status.isGranted } || // Up to date when the lifecycle is resumed
126             revokedPermissions.isEmpty() // Up to date when the user launches the action
127     }
128 
<lambda>null129     override val shouldShowRationale: Boolean by derivedStateOf {
130         permissions.any { it.status.shouldShowRationale }
131     }
132 
launchMultiplePermissionRequestnull133     override fun launchMultiplePermissionRequest() {
134         launcher?.launch(
135             permissions.map { it.permission }.toTypedArray()
136         ) ?: throw IllegalStateException("ActivityResultLauncher cannot be null")
137     }
138 
139     internal var launcher: ActivityResultLauncher<Array<String>>? = null
140 
updatePermissionsStatusnull141     internal fun updatePermissionsStatus(permissionsStatus: Map<String, Boolean>) {
142         // Update all permissions with the result
143         for (permission in permissionsStatus.keys) {
144             mutablePermissions.firstOrNull { it.permission == permission }?.apply {
145                 permissionsStatus[permission]?.let {
146                     this.refreshPermissionStatus()
147                 }
148             }
149         }
150     }
151 }
152