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