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