1 /* 2 * 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.Manifest.permission.ACCESS_MEDIA_LOCATION 20 import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED 21 import android.Manifest.permission_group.READ_MEDIA_VISUAL 22 import android.Manifest.permission_group.STORAGE 23 import android.os.Build 24 import com.android.modules.utils.build.SdkLevel 25 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup 26 import com.android.permissioncontroller.permission.ui.model.DenyButton 27 import com.android.permissioncontroller.permission.ui.model.Prompt 28 import com.android.permissioncontroller.permission.utils.KotlinUtils.isPhotoPickerPromptEnabled 29 import com.android.permissioncontroller.permission.utils.KotlinUtils.isPhotoPickerPromptSupported 30 31 /** 32 * Storage split from one group (STORAGE) into two (READ_MEDIA_VISUAL and READ_MEDIA_AURAL) in T. 33 * There are special dialogs to deal with a pre-T app requesting STORAGE on a post-T device. In U, 34 * the READ_MEDIA_VISUAL group was augmented with the option to select photos, which led to several 35 * new dialogs to handle that. 36 */ 37 object StorageGrantBehavior : GrantBehavior() { getPromptnull38 override fun getPrompt( 39 group: LightAppPermGroup, 40 requestedPerms: Set<String>, 41 isSystemTriggeredPrompt: Boolean 42 ): Prompt { 43 val appSupportsSplitStoragePermissions = appSupportsSplitStoragePermissions(group) 44 if (!SdkLevel.isAtLeastT()) { 45 return Prompt.BASIC 46 } else if (appSupportsSplitStoragePermissions && group.permGroupName == STORAGE) { 47 return Prompt.NO_UI_REJECT_THIS_GROUP 48 } 49 50 if (appSupportsSplitStoragePermissions && !shouldShowPhotoPickerPromptForApp(group)) { 51 return Prompt.BASIC 52 } 53 54 if (!appSupportsSplitStoragePermissions) { 55 if (group.packageInfo.targetSdkVersion < Build.VERSION_CODES.Q) { 56 return Prompt.STORAGE_SUPERGROUP_PRE_Q 57 } else { 58 return Prompt.STORAGE_SUPERGROUP_Q_TO_S 59 } 60 } 61 62 // Else, app supports the new photo picker dialog 63 if (requestedPerms.all { it in getPartialGrantPermissions(group) }) { 64 // Do not allow apps to request only READ_MEDIA_VISUAL_USER_SELECTED 65 return Prompt.NO_UI_REJECT_THIS_GROUP 66 } 67 68 val userSelectedPerm = group.permissions[READ_MEDIA_VISUAL_USER_SELECTED] 69 if (userSelectedPerm?.isUserFixed == true && userSelectedPerm.isGrantedIncludingAppOp) { 70 return Prompt.NO_UI_PHOTO_PICKER_REDIRECT 71 } 72 73 if (userSelectedPerm?.isGrantedIncludingAppOp == true) { 74 return Prompt.SELECT_MORE_PHOTOS 75 } else { 76 return Prompt.SELECT_PHOTOS 77 } 78 } 79 getDenyButtonnull80 override fun getDenyButton( 81 group: LightAppPermGroup, 82 requestedPerms: Set<String>, 83 prompt: Prompt 84 ): DenyButton { 85 if (prompt == Prompt.SELECT_MORE_PHOTOS) { 86 return DenyButton.DONT_SELECT_MORE 87 } 88 return BasicGrantBehavior.getDenyButton(group, requestedPerms, prompt) 89 } 90 isGroupFullyGrantednull91 override fun isGroupFullyGranted( 92 group: LightAppPermGroup, 93 requestedPerms: Set<String> 94 ): Boolean { 95 if (!isPhotoPickerPromptSupported() || group.permGroupName != READ_MEDIA_VISUAL) { 96 return super.isGroupFullyGranted(group, requestedPerms) 97 } 98 99 return group.permissions.values.any { 100 it.name !in getPartialGrantPermissions(group) && it.isGrantedIncludingAppOp 101 } 102 } 103 isPermissionFixednull104 override fun isPermissionFixed(group: LightAppPermGroup, perm: String): Boolean { 105 val userSelectedPerm = group.permissions[READ_MEDIA_VISUAL_USER_SELECTED] 106 if ( 107 userSelectedPerm != null && 108 userSelectedPerm.isGrantedIncludingAppOp && 109 userSelectedPerm.isUserFixed 110 ) { 111 // If the user selected permission is fixed and granted, we immediately show the 112 // photo picker, rather than filtering 113 return false 114 } 115 return super.isPermissionFixed(group, perm) 116 } 117 appSupportsSplitStoragePermissionsnull118 private fun appSupportsSplitStoragePermissions(group: LightAppPermGroup) = 119 SdkLevel.isAtLeastT() && group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU 120 121 private fun shouldShowPhotoPickerPromptForApp(group: LightAppPermGroup) = 122 isPhotoPickerPromptEnabled() && 123 group.permGroupName == READ_MEDIA_VISUAL && 124 (group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE || 125 appSupportsPhotoPicker(group)) 126 127 private fun appSupportsPhotoPicker(group: LightAppPermGroup) = 128 group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU && 129 group.permissions[READ_MEDIA_VISUAL_USER_SELECTED]?.isImplicit == false 130 131 private fun getPartialGrantPermissions(group: LightAppPermGroup): Set<String> { 132 return if (appSupportsPhotoPicker(group) && shouldShowPhotoPickerPromptForApp(group)) { 133 setOf(READ_MEDIA_VISUAL_USER_SELECTED, ACCESS_MEDIA_LOCATION) 134 } else { 135 setOf(READ_MEDIA_VISUAL_USER_SELECTED) 136 } 137 } 138 } 139