summaryrefslogtreecommitdiff
path: root/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BackgroundGrantBehavior.kt
diff options
context:
space:
mode:
Diffstat (limited to 'PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BackgroundGrantBehavior.kt')
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BackgroundGrantBehavior.kt210
1 files changed, 210 insertions, 0 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BackgroundGrantBehavior.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BackgroundGrantBehavior.kt
new file mode 100644
index 000000000..6234b2755
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BackgroundGrantBehavior.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.model.grantPermissions
+
+import android.os.Build
+import android.permission.PermissionManager
+import android.util.Log
+import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.ui.model.DenyButton
+import com.android.permissioncontroller.permission.ui.model.Prompt
+import com.android.permissioncontroller.permission.utils.PermissionMapping
+
+/**
+ * This behavior handles groups that respect the difference between foreground and background
+ * access. At present, this includes all one-time permissions, in addition to those with explicit
+ * background permissions.
+ *
+ * The behavior is split based on the target SDK when the app split into foreground and background
+ * (or android R, whichever is newer). If the app targets prior to the split, we show a dialog with
+ * a link to permission settings. Once the app targets after the split, we no longer allow the app
+ * to request background and foreground at the same time. If they try, we reject it. An app has to
+ * request foreground, get it granted, then request background only, we send the user to settings.
+ */
+object BackgroundGrantBehavior : GrantBehavior() {
+
+ private val splitPermissionSdkMap = buildMap {
+ for (splitPerm in
+ PermissionControllerApplication.get()
+ .getSystemService(PermissionManager::class.java)!!
+ .splitPermissions) {
+ put(splitPerm.splitPermission, splitPerm.targetSdk)
+ }
+ }
+
+ // Redundant "if" conditions are suppressed, because the conditions are clearer
+ @Suppress("KotlinConstantConditions")
+ override fun getPrompt(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ isSystemTriggeredPrompt: Boolean
+ ): Prompt {
+ val requestsBg = hasBgPerms(group, requestedPerms)
+ val requestsFg = requestedPerms.any { it !in group.backgroundPermNames }
+ val isOneTimeGroup = PermissionMapping.supportsOneTimeGrant(group.permGroupName)
+ val isFgGranted = group.foreground.isGrantedExcludingRWROrAllRWR
+ val isFgOneTime = group.foreground.isOneTime
+ val splitSdk = getSdkGroupWasSplitToBg(requestedPerms)
+ val isAppIsOlderThanSplitToBg = group.packageInfo.targetSdkVersion < splitSdk
+
+ if (!requestsBg && !isOneTimeGroup) {
+ return Prompt.FG_ONLY
+ } else if (!requestsBg) {
+ return Prompt.ONE_TIME_FG
+ }
+
+ if (requestsBg && !requestsFg && !isFgGranted) {
+ Log.w(
+ LOG_TAG,
+ "Cannot grant ${group.permGroupName} as the foreground permissions" +
+ " are not requested or already granted."
+ )
+ return Prompt.NO_UI_REJECT_THIS_GROUP
+ }
+
+ return if (isAppIsOlderThanSplitToBg) {
+ getPromptForOlderApp(isOneTimeGroup, requestsFg, isFgGranted, isFgOneTime)
+ } else {
+ getPromptForNewerApp(group.permGroupName, splitSdk, requestsFg, isFgGranted)
+ }
+ }
+
+ /**
+ * Get the prompt for an app that targets before the sdk level where the permission group was
+ * split into foreground and background.
+ */
+ private fun getPromptForOlderApp(
+ isOneTimeGroup: Boolean,
+ requestsFg: Boolean,
+ isFgGranted: Boolean,
+ isFgOneTime: Boolean
+ ): Prompt {
+ if (requestsFg && !isFgGranted) {
+ if (isOneTimeGroup) {
+ return Prompt.SETTINGS_LINK_WITH_OT
+ }
+ return Prompt.SETTINGS_LINK_FOR_BG
+ }
+
+ if (isFgGranted) {
+ if (isFgOneTime) {
+ return Prompt.OT_UPGRADE_SETTINGS_LINK
+ }
+ return Prompt.UPGRADE_SETTINGS_LINK
+ }
+ return Prompt.NO_UI_REJECT_ALL_GROUPS
+ }
+
+ /**
+ * Get the prompt for an app that targets at least the sdk level where the permission group was
+ * split into foreground and background.
+ */
+ private fun getPromptForNewerApp(
+ groupName: String,
+ splitSdk: Int,
+ requestsFg: Boolean,
+ isFgGranted: Boolean
+ ): Prompt {
+ if (!requestsFg && isFgGranted) {
+ return Prompt.NO_UI_SETTINGS_REDIRECT
+ }
+
+ if (requestsFg) {
+ Log.e(
+ LOG_TAG,
+ "For SDK $splitSdk+ apps requesting $groupName, " +
+ "background permissions must be requested alone after foreground permissions " +
+ "are already granted"
+ )
+ return Prompt.NO_UI_REJECT_ALL_GROUPS
+ } else if (!isFgGranted) {
+ Log.e(
+ LOG_TAG,
+ "For SDK $splitSdk+ apps requesting, $groupName, " +
+ "background permissions must be requested after foreground permissions are " +
+ "already granted"
+ )
+ Prompt.NO_UI_REJECT_THIS_GROUP
+ }
+ return Prompt.NO_UI_REJECT_ALL_GROUPS
+ }
+
+ override fun getDenyButton(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ prompt: Prompt
+ ): DenyButton {
+ val basicDenyBehavior = BasicGrantBehavior.getDenyButton(group, requestedPerms, prompt)
+ if (prompt == Prompt.UPGRADE_SETTINGS_LINK || prompt == Prompt.OT_UPGRADE_SETTINGS_LINK) {
+ if (basicDenyBehavior == DenyButton.DENY) {
+ return if (prompt == Prompt.UPGRADE_SETTINGS_LINK) {
+ DenyButton.NO_UPGRADE
+ } else {
+ DenyButton.NO_UPGRADE_OT
+ }
+ }
+ return if (prompt == Prompt.UPGRADE_SETTINGS_LINK) {
+ DenyButton.NO_UPGRADE_AND_DONT_ASK_AGAIN
+ } else {
+ DenyButton.NO_UPGRADE_AND_DONT_ASK_AGAIN_OT
+ }
+ }
+ return basicDenyBehavior
+ }
+
+ override fun isGroupFullyGranted(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>
+ ): Boolean {
+ return (!hasBgPerms(group, requestedPerms) ||
+ group.background.isGrantedExcludingRWROrAllRWR) &&
+ group.foreground.isGrantedExcludingRWROrAllRWR
+ }
+
+ override fun isForegroundFullyGranted(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>
+ ): Boolean {
+ return group.foreground.isGrantedExcludingRWROrAllRWR
+ }
+
+ override fun isPermissionFixed(group: LightAppPermGroup, perm: String): Boolean {
+ if (perm in group.backgroundPermNames) {
+ return group.background.isUserFixed
+ }
+ return group.foreground.isUserFixed
+ }
+
+ private fun hasBgPerms(group: LightAppPermGroup, requestedPerms: Set<String>): Boolean {
+ return requestedPerms.any { it in group.backgroundPermNames }
+ }
+
+ private fun getSdkGroupWasSplitToBg(requestedPerms: Set<String>): Int {
+ val splitSdks = requestedPerms.mapNotNull { perm -> splitPermissionSdkMap[perm] }
+
+ if (splitSdks.isEmpty()) {
+ // If there was no split found, assume the split happened in R. This applies to
+ // Mic and Camera, which technically split in S, but were treated as foreground-only
+ // in R
+ return Build.VERSION_CODES.R
+ }
+ // The background permission request UI changed in R, so if a split happened before R,
+ // treat it as if it happened in R
+ return maxOf(splitSdks.min(), Build.VERSION_CODES.R)
+ }
+}