1 /*
<lambda>null2  * Copyright (C) 2023 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  *      http://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.android.permissioncontroller.permission.ui.model.grantPermissions
18 
19 import android.os.Build
20 import android.permission.PermissionManager
21 import android.util.Log
22 import com.android.permissioncontroller.PermissionControllerApplication
23 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
24 import com.android.permissioncontroller.permission.ui.model.DenyButton
25 import com.android.permissioncontroller.permission.ui.model.Prompt
26 import com.android.permissioncontroller.permission.utils.PermissionMapping
27 
28 /**
29  * This behavior handles groups that respect the difference between foreground and background
30  * access. At present, this includes all one-time permissions, in addition to those with explicit
31  * background permissions.
32  *
33  * The behavior is split based on the target SDK when the app split into foreground and background
34  * (or android R, whichever is newer). If the app targets prior to the split, we show a dialog with
35  * a link to permission settings. Once the app targets after the split, we no longer allow the app
36  * to request background and foreground at the same time. If they try, we reject it. An app has to
37  * request foreground, get it granted, then request background only, we send the user to settings.
38  */
39 object BackgroundGrantBehavior : GrantBehavior() {
40 
41     private val splitPermissionSdkMap = buildMap {
42         for (splitPerm in
43             PermissionControllerApplication.get()
44                 .getSystemService(PermissionManager::class.java)!!
45                 .splitPermissions) {
46             put(splitPerm.splitPermission, splitPerm.targetSdk)
47         }
48     }
49 
50     // Redundant "if" conditions are suppressed, because the conditions are clearer
51     @Suppress("KotlinConstantConditions")
52     override fun getPrompt(
53         group: LightAppPermGroup,
54         requestedPerms: Set<String>,
55         isSystemTriggeredPrompt: Boolean
56     ): Prompt {
57         val requestsBg = hasBgPerms(group, requestedPerms)
58         val requestsFg = requestedPerms.any { it !in group.backgroundPermNames }
59         val isOneTimeGroup = PermissionMapping.supportsOneTimeGrant(group.permGroupName)
60         val isFgGranted = group.foreground.isGrantedExcludingRWROrAllRWR
61         val isFgOneTime = group.foreground.isOneTime
62         val splitSdk = getSdkGroupWasSplitToBg(requestedPerms)
63         val isAppIsOlderThanSplitToBg = group.packageInfo.targetSdkVersion < splitSdk
64 
65         if (!requestsBg && !isOneTimeGroup) {
66             return Prompt.FG_ONLY
67         } else if (!requestsBg) {
68             return Prompt.ONE_TIME_FG
69         }
70 
71         if (requestsBg && !requestsFg && !isFgGranted) {
72             Log.w(
73                 LOG_TAG,
74                 "Cannot grant ${group.permGroupName} as the foreground permissions" +
75                     " are not requested or already granted."
76             )
77             return Prompt.NO_UI_REJECT_THIS_GROUP
78         }
79 
80         return if (isAppIsOlderThanSplitToBg) {
81             getPromptForOlderApp(isOneTimeGroup, requestsFg, isFgGranted, isFgOneTime)
82         } else {
83             getPromptForNewerApp(group.permGroupName, splitSdk, requestsFg, isFgGranted)
84         }
85     }
86 
87     /**
88      * Get the prompt for an app that targets before the sdk level where the permission group was
89      * split into foreground and background.
90      */
91     private fun getPromptForOlderApp(
92         isOneTimeGroup: Boolean,
93         requestsFg: Boolean,
94         isFgGranted: Boolean,
95         isFgOneTime: Boolean
96     ): Prompt {
97         if (requestsFg && !isFgGranted) {
98             if (isOneTimeGroup) {
99                 return Prompt.SETTINGS_LINK_WITH_OT
100             }
101             return Prompt.SETTINGS_LINK_FOR_BG
102         }
103 
104         if (isFgGranted) {
105             if (isFgOneTime) {
106                 return Prompt.OT_UPGRADE_SETTINGS_LINK
107             }
108             return Prompt.UPGRADE_SETTINGS_LINK
109         }
110         return Prompt.NO_UI_REJECT_ALL_GROUPS
111     }
112 
113     /**
114      * Get the prompt for an app that targets at least the sdk level where the permission group was
115      * split into foreground and background.
116      */
117     private fun getPromptForNewerApp(
118         groupName: String,
119         splitSdk: Int,
120         requestsFg: Boolean,
121         isFgGranted: Boolean
122     ): Prompt {
123         if (!requestsFg && isFgGranted) {
124             return Prompt.NO_UI_SETTINGS_REDIRECT
125         }
126 
127         if (requestsFg) {
128             Log.e(
129                 LOG_TAG,
130                 "For SDK $splitSdk+ apps requesting $groupName, " +
131                     "background permissions must be requested alone after foreground permissions " +
132                     "are already granted"
133             )
134             return Prompt.NO_UI_REJECT_ALL_GROUPS
135         } else if (!isFgGranted) {
136             Log.e(
137                 LOG_TAG,
138                 "For SDK $splitSdk+ apps requesting, $groupName, " +
139                     "background permissions must be requested after foreground permissions are " +
140                     "already granted"
141             )
142             Prompt.NO_UI_REJECT_THIS_GROUP
143         }
144         return Prompt.NO_UI_REJECT_ALL_GROUPS
145     }
146 
147     override fun getDenyButton(
148         group: LightAppPermGroup,
149         requestedPerms: Set<String>,
150         prompt: Prompt
151     ): DenyButton {
152         val basicDenyBehavior = BasicGrantBehavior.getDenyButton(group, requestedPerms, prompt)
153         if (prompt == Prompt.UPGRADE_SETTINGS_LINK || prompt == Prompt.OT_UPGRADE_SETTINGS_LINK) {
154             if (basicDenyBehavior == DenyButton.DENY) {
155                 return if (prompt == Prompt.UPGRADE_SETTINGS_LINK) {
156                     DenyButton.NO_UPGRADE
157                 } else {
158                     DenyButton.NO_UPGRADE_OT
159                 }
160             }
161             return if (prompt == Prompt.UPGRADE_SETTINGS_LINK) {
162                 DenyButton.NO_UPGRADE_AND_DONT_ASK_AGAIN
163             } else {
164                 DenyButton.NO_UPGRADE_AND_DONT_ASK_AGAIN_OT
165             }
166         }
167         return basicDenyBehavior
168     }
169 
170     override fun isGroupFullyGranted(
171         group: LightAppPermGroup,
172         requestedPerms: Set<String>
173     ): Boolean {
174         return (!hasBgPerms(group, requestedPerms) ||
175             group.background.isGrantedExcludingRWROrAllRWR) &&
176             group.foreground.isGrantedExcludingRWROrAllRWR
177     }
178 
179     override fun isForegroundFullyGranted(
180         group: LightAppPermGroup,
181         requestedPerms: Set<String>
182     ): Boolean {
183         return group.foreground.isGrantedExcludingRWROrAllRWR
184     }
185 
186     override fun isPermissionFixed(group: LightAppPermGroup, perm: String): Boolean {
187         if (perm in group.backgroundPermNames) {
188             return group.background.isUserFixed
189         }
190         return group.foreground.isUserFixed
191     }
192 
193     private fun hasBgPerms(group: LightAppPermGroup, requestedPerms: Set<String>): Boolean {
194         return requestedPerms.any { it in group.backgroundPermNames }
195     }
196 
197     private fun getSdkGroupWasSplitToBg(requestedPerms: Set<String>): Int {
198         val splitSdks = requestedPerms.mapNotNull { perm -> splitPermissionSdkMap[perm] }
199 
200         if (splitSdks.isEmpty()) {
201             // If there was no split found, assume the split happened in R. This applies to
202             // Mic and Camera, which technically split in S, but were treated as foreground-only
203             // in R
204             return Build.VERSION_CODES.R
205         }
206         // The background permission request UI changed in R, so if a split happened before R,
207         // treat it as if it happened in R
208         return maxOf(splitSdks.min(), Build.VERSION_CODES.R)
209     }
210 }
211