diff options
author | Nate Myren <ntmyren@google.com> | 2023-03-07 15:12:47 -0800 |
---|---|---|
committer | Nate Myren <ntmyren@google.com> | 2023-03-10 17:05:25 +0000 |
commit | 0ed5ef371b9dbe39f32a8a32af42f557b3958609 (patch) | |
tree | 1d6ea6389c951e0887d913cdd32e46ff22b7b2cb | |
parent | e2bad242d21b76689c2bc5ffc27892ee3c1c8820 (diff) | |
download | Permission-0ed5ef371b9dbe39f32a8a32af42f557b3958609.tar.gz |
Treat AML as a "partial grant" for apps that support photo picker
When an app supports the photo picker permission prompt (by targeting T+
and explicitly requesting READ_MEDIA_VISUAL_USER_SELECTED), then treat
the ACCESS_MEDIA_LOCATION permission as a "partial grant", given when
clicking "select photos" or "allow all", and not silently expanding if
READ_MEDIA_IMAGES/VIDEO are requested later
Bug: 268075614
Test: atest PhotoPickerPermissionTest
Change-Id: I5ba2dadeee3e6e4548c9eab2f859a0d6bd046a9d
4 files changed, 81 insertions, 25 deletions
diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml index 7c01ea800..13423312d 100644 --- a/PermissionController/res/values/strings.xml +++ b/PermissionController/res/values/strings.xml @@ -74,10 +74,10 @@ <string name="grant_dialog_button_always_allow_all">Always allow all</string> <!-- Title for the dialog button to allow access to select photos to be shared. [CHAR LIMIT=60] --> - <string name="grant_dialog_button_allow_selected_photos">Select photos</string> + <string name="grant_dialog_button_allow_selected_photos">Select photos and videos</string> <!-- Title for the dialog button to allow access to select more photos to be shared. [CHAR LIMIT=60] --> - <string name="grant_dialog_button_allow_more_selected_photos">Select more photos and videos</string> + <string name="grant_dialog_button_allow_more_selected_photos">Select more</string> <!-- Title for the dialog button to not allow access to select more photos to be shared. [CHAR LIMIT=60] --> <string name="grant_dialog_button_dont_allow_more_selected_photos">Don\u2019t select more</string> diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt index f63539e27..2fd6a0cac 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt @@ -20,7 +20,6 @@ package com.android.permissioncontroller.permission.ui.model import android.Manifest import android.Manifest.permission.ACCESS_COARSE_LOCATION import android.Manifest.permission.ACCESS_FINE_LOCATION -import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED import android.Manifest.permission_group.READ_MEDIA_VISUAL import android.annotation.SuppressLint import android.app.Activity @@ -83,6 +82,7 @@ import com.android.permissioncontroller.permission.utils.KotlinUtils.isPermissio import com.android.permissioncontroller.permission.utils.KotlinUtils.isPhotoPickerPromptEnabled import com.android.permissioncontroller.permission.utils.LocationUtils import com.android.permissioncontroller.permission.utils.PermissionMapping +import com.android.permissioncontroller.permission.utils.PermissionMapping.getPartialStorageGrantPermissionsForGroup import com.android.permissioncontroller.permission.utils.PermissionRationales import com.android.permissioncontroller.permission.utils.SafetyNetLogger import com.android.permissioncontroller.permission.utils.Utils @@ -347,10 +347,8 @@ class AppPermissionViewModel( selectState.isShown = true deniedState.isChecked = !group.isGranted - val onlyUserSelectedGranted = group.isGranted && group.permissions.values.all { - it.name == READ_MEDIA_VISUAL_USER_SELECTED || !it.isGrantedIncludingAppOp } - selectState.isChecked = onlyUserSelectedGranted - allowedState.isChecked = group.isGranted && !onlyUserSelectedGranted + selectState.isChecked = isPartialStorageGrant(group) + allowedState.isChecked = group.isGranted && !isPartialStorageGrant(group) } else { // Allow / Deny case allowedState.isShown = true @@ -653,12 +651,12 @@ class AppPermissionViewModel( } if (changeRequest == ChangeRequest.PHOTOS_SELECTED) { - val nonSelectedPerms = group.permissions.keys.filter { - it != READ_MEDIA_VISUAL_USER_SELECTED } + val partialGrantPerms = getPartialStorageGrantPermissionsForGroup(group) + val nonSelectedPerms = group.permissions.keys.filter { it !in partialGrantPerms } var newGroup = KotlinUtils.revokeForegroundRuntimePermissions(app, group, filterPermissions = nonSelectedPerms) newGroup = KotlinUtils.grantForegroundRuntimePermissions(app, newGroup, - filterPermissions = listOf(READ_MEDIA_VISUAL_USER_SELECTED)) + filterPermissions = partialGrantPerms.toList()) logPermissionChanges(group, newGroup, buttonClicked) return } @@ -1105,6 +1103,24 @@ class AppPermissionViewModel( "$uid packageName=$packageName" + "permGroupName=$permGroupName") } + + /** + * A partial storage grant happens when: + * An app which doesn't support the photo picker has READ_MEDIA_VISUAL_USER_SELECTED granted, or + * An app which does support the photo picker has READ_MEDIA_VISUAL_USER_SELECTED and/or + * ACCESS_MEDIA_LOCATION granted + */ + private fun isPartialStorageGrant(group: LightAppPermGroup): Boolean { + if (!isPhotoPickerPromptEnabled() || group.permGroupName != READ_MEDIA_VISUAL) { + return false + } + + val partialPerms = getPartialStorageGrantPermissionsForGroup(group) + + return group.isGranted && group.permissions.values.all { + it.name in partialPerms || (it.name !in partialPerms && !it.isGrantedIncludingAppOp) + } + } } /** diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt index 86ce85039..b311afc00 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt @@ -128,6 +128,7 @@ import com.android.permissioncontroller.permission.utils.KotlinUtils.isPermissio import com.android.permissioncontroller.permission.utils.KotlinUtils.revokeBackgroundRuntimePermissions import com.android.permissioncontroller.permission.utils.KotlinUtils.revokeForegroundRuntimePermissions import com.android.permissioncontroller.permission.utils.PermissionMapping +import com.android.permissioncontroller.permission.utils.PermissionMapping.getPartialStorageGrantPermissionsForGroup import com.android.permissioncontroller.permission.utils.PermissionRationales import com.android.permissioncontroller.permission.utils.SafetyNetLogger import com.android.permissioncontroller.permission.utils.Utils @@ -381,7 +382,7 @@ class GrantPermissionsViewModel( groupState.affectedPermissions == listOf(READ_MEDIA_VISUAL_USER_SELECTED)) { requestInfos.add(RequestInfo(groupInfo, openPhotoPicker = true)) continue - } else if (isVisualUserSelectedOnlyGranted(groupState.group)) { + } else if (isPartialStorageGrant(groupState.group)) { // More photos dialog message = RequestMessage.MORE_PHOTOS_MESSAGE buttonVisibilities[ALLOW_BUTTON] = false @@ -763,9 +764,10 @@ class GrantPermissionsViewModel( // is still grantable. return true } - } else if (perm == READ_MEDIA_VISUAL_USER_SELECTED && + } else if (perm in getPartialStorageGrantPermissionsForGroup(group) && lightPermission.isGrantedIncludingAppOp) { - // If USER_SELECTED is granted as fixed, we should immediately show the photo picker + // If a partial storage permission is granted as fixed, we should immediately show + // the photo picker return true } reportRequestResult(perm, @@ -795,11 +797,14 @@ class GrantPermissionsViewModel( group.foreground.isUserSet) { return STATE_SKIPPED } else if (perm == READ_MEDIA_VISUAL_USER_SELECTED) { + val partialPerms = getPartialStorageGrantPermissionsForGroup(group) val otherRequestedPerms = unfilteredAffectedPermissions.filter { otherPerm -> - otherPerm in group.permissions && otherPerm != READ_MEDIA_VISUAL_USER_SELECTED + otherPerm in group.permissions && otherPerm !in partialPerms } if (otherRequestedPerms.isEmpty()) { - // If the user only requested USER_SELECTED, skip the request + // If the app requested USER_SELECTED while not supporting the photo picker, or if + // the app explicitly requested only USER_SELECTED and/or ACCESS_MEDIA_LOCATION, + // then skip the request return STATE_SKIPPED } } @@ -861,18 +866,27 @@ class GrantPermissionsViewModel( } // If READ_MEDIA_VISUAL_USER_SELECTED is the only permission in the group that is granted, // do not grant. - if (isVisualUserSelectedOnlyGranted(group) || - HEALTH_PERMISSION_GROUP == group.permGroupName) { + if (isPartialStorageGrant(group) || HEALTH_PERMISSION_GROUP == group.permGroupName) { return false } return true } - private fun isVisualUserSelectedOnlyGranted(group: LightAppPermGroup): Boolean { - return KotlinUtils.isPhotoPickerPromptEnabled() && - group.permGroupName == READ_MEDIA_VISUAL && group.permissions.values.all { - (it.name == READ_MEDIA_VISUAL_USER_SELECTED) || !it.isGrantedIncludingAppOp } && - group.permissions[READ_MEDIA_VISUAL_USER_SELECTED]?.isGrantedIncludingAppOp == true + /** + * A partial storage grant happens when: + * An app which doesn't support the photo picker has READ_MEDIA_VISUAL_USER_SELECTED granted, or + * An app which does support the photo picker has READ_MEDIA_VISUAL_USER_SELECTED and/or + * ACCESS_MEDIA_LOCATION granted + */ + private fun isPartialStorageGrant(group: LightAppPermGroup): Boolean { + if (!KotlinUtils.isPhotoPickerPromptEnabled() || group.permGroupName != READ_MEDIA_VISUAL) { + return false + } + + val partialPerms = getPartialStorageGrantPermissionsForGroup(group) + return group.isGranted && group.permissions.values.all { + it.name in partialPerms || (it.name !in partialPerms && !it.isGrantedIncludingAppOp) + } } private fun getStateFromPolicy(perm: String, group: LightAppPermGroup): Int { @@ -1060,11 +1074,11 @@ class GrantPermissionsViewModel( appPermGroup.setSelfRevoked() appPermGroup.persistChanges(false, null, nonSelectedPerms.toSet()) } else { - val nonSelectedPerms = groupState.affectedPermissions - .filter { it != READ_MEDIA_VISUAL_USER_SELECTED } + val partialPerms = getPartialStorageGrantPermissionsForGroup(groupState.group) + val nonSelectedPerms = groupState.affectedPermissions.filter { it !in partialPerms } val setUserFixed = userSelectedPerm.isUserFixed || userSelectedPerm.isUserSet grantForegroundRuntimePermissions(app, groupState.group, - listOf(READ_MEDIA_VISUAL_USER_SELECTED), userFixed = setUserFixed) + partialPerms.toList(), userFixed = setUserFixed) revokeForegroundRuntimePermissions(app, groupState.group, userFixed = setUserFixed, oneTime = false, filterPermissions = nonSelectedPerms) } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt index c0b174c28..69b9af7e7 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt @@ -23,6 +23,7 @@ import android.content.pm.PermissionInfo import android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP import android.util.Log import com.android.modules.utils.build.SdkLevel +import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup /** * This file contains the canonical mapping of permission to permission group, used in the @@ -48,6 +49,8 @@ object PermissionMapping { Manifest.permission_group.READ_MEDIA_AURAL, Manifest.permission_group.READ_MEDIA_VISUAL) + val PARTIAL_MEDIA_PERMISSIONS: MutableSet<String> = mutableSetOf() + /** Mapping permission -> group for all dangerous platform permissions */ private val PLATFORM_PERMISSIONS: MutableMap<String, String> = mutableMapOf() @@ -59,6 +62,7 @@ object PermissionMapping { private val HEALTH_PERMISSIONS_SET: MutableSet<String> = mutableSetOf() + init { PLATFORM_PERMISSIONS[Manifest.permission.READ_CONTACTS] = Manifest.permission_group.CONTACTS PLATFORM_PERMISSIONS[Manifest.permission.WRITE_CONTACTS] = @@ -188,6 +192,11 @@ object PermissionMapping { ONE_TIME_PERMISSION_GROUPS.add(Manifest.permission_group.LOCATION) ONE_TIME_PERMISSION_GROUPS.add(Manifest.permission_group.CAMERA) ONE_TIME_PERMISSION_GROUPS.add(Manifest.permission_group.MICROPHONE) + + if (SdkLevel.isAtLeastU()) { + PARTIAL_MEDIA_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) + PARTIAL_MEDIA_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION) + } } /** @@ -321,6 +330,23 @@ object PermissionMapping { } /** + * Get the permissions that, if granted, are considered a "partial grant" of the + * READ_MEDIA_VISUAL permission group. If the app declares READ_MEDIA_VISUAL_USER_SELECTED, then + * both READ_MEDIA_VISUAL_USER_SELECTED and ACCESS_MEDIA_LOCATION are considered a partial + * grant. Otherwise, ACCESS_MEDIA_LOCATION is considered a full grant (for compatibility). + */ + fun getPartialStorageGrantPermissionsForGroup(group: LightAppPermGroup): Set<String> { + val appSupportsPickerPrompt = group + .permissions[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED]?.isImplicit == false + + return if (appSupportsPickerPrompt) { + PARTIAL_MEDIA_PERMISSIONS + } else { + setOf(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) + } + } + + /** * Returns true if the given permission is a health platform permission. */ @JvmStatic |