• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.wear
18 
19 import android.app.Application
20 import android.content.Context
21 import android.graphics.drawable.Drawable
22 import android.os.UserHandle
23 import com.android.permission.flags.Flags
24 import com.android.permissioncontroller.R
25 import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
26 import com.android.permissioncontroller.permission.ui.Category
27 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel
28 import com.android.permissioncontroller.permission.ui.wear.model.WearAppPermissionUsagesViewModel
29 import com.android.permissioncontroller.permission.ui.wear.model.WearLocationProviderInterceptDialogViewModel
30 import com.android.permissioncontroller.permission.utils.KotlinUtils
31 import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupDescription
32 import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel
33 import com.android.permissioncontroller.permission.utils.LocationUtils
34 import com.android.settingslib.utils.applications.AppUtils
35 import java.text.Collator
36 import java.util.Random
37 
38 /** Helper class for WearPermissionsAppScreen. */
39 class WearPermissionAppsHelper(
40     val application: Application,
41     val context: Context,
42     val permGroupName: String,
43     val viewModel: PermissionAppsViewModel,
44     val wearViewModel: WearAppPermissionUsagesViewModel,
45     val locationProviderDialogViewModel: WearLocationProviderInterceptDialogViewModel,
46     private val isStorageAndLessThanT: Boolean,
47     private val onAppClick: (String, UserHandle, String) -> Unit,
48     val onShowSystemClick: (Boolean) -> Unit,
49     val logFragmentCreated: (String, UserHandle, Long, Boolean, Boolean, Boolean) -> Unit
50 ) {
51     fun categorizedAppsLiveData() = viewModel.categorizedAppsLiveData
52 
53     fun hasSystemAppsLiveData() = viewModel.hasSystemAppsLiveData
54 
55     fun shouldShowSystemLiveData() = viewModel.shouldShowSystemLiveData
56 
57     fun showAlways() = viewModel.showAllowAlwaysStringLiveData.value ?: false
58 
59     fun getTitle() = getPermGroupLabel(application, permGroupName).toString()
60 
61     fun getSubTitle() = getPermGroupDescription(application, permGroupName).toString()
62 
63     fun getChipsByCategory(
64         categorizedApps: Map<Category, List<Pair<String, UserHandle>>>,
65         appPermissionUsages: List<AppPermissionUsage>
66     ): Map<String, List<ChipInfo>> {
67         val chipsByCategory: MutableMap<String, MutableList<ChipInfo>> = HashMap()
68 
69         // A mapping of user + packageName to their last access timestamps for the permission group.
70         val groupUsageLastAccessTime: Map<String, Long> =
71             viewModel.extractGroupUsageLastAccessTime(appPermissionUsages)
72 
73         val context = application
74         val collator = Collator.getInstance(context.resources.configuration.locales.get(0))
75         val comparator = ChipComparator(collator)
76 
77         val viewIdForLogging = Random().nextLong()
78         for (category in Category.entries) {
79             if (category == Category.ALLOWED && isStorageAndLessThanT) {
80                 val allowedList = categorizedApps[Category.ALLOWED]
81                 if (!allowedList.isNullOrEmpty()) {
82                     allowedList
83                         .partition { p -> viewModel.packageHasFullStorage(p.first, p.second) }
84                         .let {
85                             if (it.first.isNotEmpty()) {
86                                 chipsByCategory[STORAGE_ALLOWED_FULL] =
87                                     convertToChips(
88                                         category,
89                                         it.first,
90                                         viewIdForLogging,
91                                         comparator,
92                                         groupUsageLastAccessTime
93                                     )
94                             }
95                             if (it.second.isNotEmpty()) {
96                                 chipsByCategory[STORAGE_ALLOWED_SCOPED] =
97                                     convertToChips(
98                                         category,
99                                         it.second,
100                                         viewIdForLogging,
101                                         comparator,
102                                         groupUsageLastAccessTime
103                                     )
104                             }
105                         }
106                 }
107                 continue
108             }
109             val list = categorizedApps[category]
110             if (!list.isNullOrEmpty()) {
111                 chipsByCategory[category.categoryName] =
112                     convertToChips(
113                         category,
114                         list,
115                         viewIdForLogging,
116                         comparator,
117                         groupUsageLastAccessTime
118                     )
119             }
120         }
121 
122         // Add no_apps chips to allowed and denied if it doesn't have an app.
123         addNoAppsIfNeeded(chipsByCategory)
124         return chipsByCategory
125     }
126 
127     private fun convertToChips(
128         category: Category,
129         list: List<Pair<String, UserHandle>>,
130         viewIdForLogging: Long,
131         comparator: Comparator<ChipInfo>,
132         groupUsageLastAccessTime: Map<String, Long>
133     ) =
134         list
135             .map { p ->
136                 val lastAccessTime = groupUsageLastAccessTime[(p.second.toString() + p.first)]
137                 createAppChipInfo(
138                     application,
139                     p.first,
140                     p.second,
141                     category,
142                     onAppClick,
143                     viewIdForLogging,
144                     lastAccessTime
145                 )
146             }
147             .sortedWith(comparator)
148             .toMutableList()
149 
150     fun setCreationLogged(isLogged: Boolean) {
151         viewModel.creationLogged = isLogged
152     }
153 
154     private fun createAppChipInfo(
155         application: Application,
156         packageName: String,
157         user: UserHandle,
158         category: Category,
159         onClick: (packageName: String, user: UserHandle, category: String) -> Unit,
160         viewIdForLogging: Long,
161         lastAccessTime: Long?
162     ): ChipInfo {
163         if (!viewModel.creationLogged) {
164             logFragmentCreated(
165                 packageName,
166                 user,
167                 viewIdForLogging,
168                 category == Category.ALLOWED,
169                 category == Category.ALLOWED_FOREGROUND,
170                 category == Category.DENIED
171             )
172         }
173         val summary =
174             if (Flags.wearPrivacyDashboardEnabledReadOnly()) {
175                 lastAccessTime?.let { WearUtils.getPreferenceSummary(application, lastAccessTime) }
176             } else {
177                 null
178             }
179         return ChipInfo(
180             title = KotlinUtils.getPackageLabel(application, packageName, user),
181             summary = summary,
182             contentDescription =
183                 AppUtils.getAppContentDescription(application, packageName, user.identifier),
184             icon = KotlinUtils.getBadgedPackageIcon(application, packageName, user),
185             onClick = {
186                 if (
187                     LocationUtils.isLocationGroupAndProvider(
188                         application.applicationContext,
189                         permGroupName,
190                         packageName
191                     )
192                 ) {
193                     locationProviderDialogViewModel.showDialog(context, packageName)
194                 } else {
195                     onClick(packageName, user, category.categoryName)
196                 }
197             }
198         )
199     }
200 
201     private fun addNoAppsIfNeeded(chipsByCategory: MutableMap<String, MutableList<ChipInfo>>) {
202         addNoAppsToAllowed(chipsByCategory)
203         addNoAppsToDenied(chipsByCategory)
204     }
205 
206     private fun addNoAppsToAllowed(chipsByCategory: MutableMap<String, MutableList<ChipInfo>>) {
207         if (isStorageAndLessThanT) {
208             // For the storage permission,
209             // allowed category is split into allowed_full and allowed_scoped categories,
210             // add no_apps chip to the categories.
211             addNoAppsTo(chipsByCategory, STORAGE_ALLOWED_FULL, R.string.no_apps_allowed_full)
212             addNoAppsTo(chipsByCategory, STORAGE_ALLOWED_SCOPED, R.string.no_apps_allowed_scoped)
213             return
214         }
215         addNoAppsTo(chipsByCategory, Category.ALLOWED.categoryName, R.string.no_apps_allowed)
216     }
217 
218     private fun addNoAppsToDenied(chipsByCategory: MutableMap<String, MutableList<ChipInfo>>) {
219         addNoAppsTo(chipsByCategory, Category.DENIED.categoryName, R.string.no_apps_denied)
220     }
221 
222     private fun addNoAppsTo(
223         chipsByCategory: MutableMap<String, MutableList<ChipInfo>>,
224         categoryName: String,
225         titleResId: Int
226     ) {
227         if (chipsByCategory[categoryName].isNullOrEmpty()) {
228             chipsByCategory[categoryName] =
229                 mutableListOf(
230                     ChipInfo(title = application.resources.getString(titleResId), enabled = false)
231                 )
232         }
233     }
234 
235     companion object {
236         private const val STORAGE_ALLOWED_FULL = "allowed_storage_full"
237         private const val STORAGE_ALLOWED_SCOPED = "allowed_storage_scoped"
238     }
239 }
240 
241 class ChipInfo(
242     val title: String,
243     val summary: String? = null,
244     val contentDescription: String? = null,
<lambda>null245     val onClick: () -> Unit = {},
246     val icon: Drawable? = null,
247     val enabled: Boolean = true
248 )
249 
250 internal class ChipComparator(val collator: Collator) : Comparator<ChipInfo> {
comparenull251     override fun compare(lhs: ChipInfo, rhs: ChipInfo): Int {
252         var result = collator.compare(lhs.title, rhs.title)
253         if (result == 0) {
254             result = lhs.title.compareTo(rhs.title)
255         }
256         return result
257     }
258 }
259