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