1 /* <lambda>null2 * Copyright (C) 2022 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.v31 18 19 import android.Manifest 20 import android.app.LoaderManager 21 import android.app.role.RoleManager 22 import android.content.Context 23 import android.os.Build 24 import android.util.ArrayMap 25 import android.util.ArraySet 26 import android.util.Log 27 import androidx.annotation.RequiresApi 28 import androidx.lifecycle.ViewModel 29 import androidx.lifecycle.ViewModelProvider 30 import com.android.permissioncontroller.permission.model.AppPermissionGroup 31 import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage 32 import com.android.permissioncontroller.permission.model.v31.PermissionUsages 33 import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp 34 import com.android.permissioncontroller.permission.ui.handheld.v31.is7DayToggleEnabled 35 import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel 36 import com.android.permissioncontroller.permission.utils.Utils 37 import java.time.Instant 38 import java.util.concurrent.TimeUnit 39 import kotlin.math.max 40 41 @RequiresApi(Build.VERSION_CODES.S) 42 class PermissionUsageViewModel(val roleManager: RoleManager) : ViewModel() { 43 44 companion object { 45 private const val LOG_TAG = "PermissionUsageViewModel" 46 47 /** TODO(ewol): Use the config setting to determine amount of time to show. */ 48 private val TIME_FILTER_MILLIS = TimeUnit.DAYS.toMillis(7) 49 private val TIME_7_DAYS_DURATION = TimeUnit.DAYS.toMillis(7) 50 private val TIME_24_HOURS_DURATION = TimeUnit.DAYS.toMillis(1) 51 52 @JvmStatic 53 val PERMISSION_GROUP_ORDER: Map<String, Int> = mapOf( 54 Manifest.permission_group.LOCATION to 0, 55 Manifest.permission_group.CAMERA to 1, 56 Manifest.permission_group.MICROPHONE to 2 57 ) 58 private const val DEFAULT_ORDER = 3 59 } 60 61 fun loadPermissionUsages( 62 loaderManager: LoaderManager, 63 permissionUsages: PermissionUsages, 64 callback: PermissionUsages.PermissionsUsagesChangeCallback 65 ) { 66 val filterTimeBeginMillis = max(System.currentTimeMillis() - TIME_FILTER_MILLIS, 67 Instant.EPOCH.toEpochMilli()) 68 permissionUsages.load(null /*filterPackageName*/, null /*filterPermissionGroups*/, 69 filterTimeBeginMillis, Long.MAX_VALUE, PermissionUsages.USAGE_FLAG_LAST 70 or PermissionUsages.USAGE_FLAG_HISTORICAL, loaderManager, 71 false /*getUiInfo*/, false /*getNonPlatformPermissions*/, callback /*callback*/, 72 false /*sync*/) 73 } 74 75 fun extractUsages( 76 permissionUsages: List<AppPermissionUsage>, 77 show7Days: Boolean, 78 showSystem: Boolean 79 ): Triple<MutableMap<String, Int>, ArrayList<PermissionApp>, Boolean> { 80 val curTime = System.currentTimeMillis() 81 val showPermissionUsagesDuration = if (is7DayToggleEnabled() && show7Days) { 82 TIME_7_DAYS_DURATION 83 } else { 84 TIME_24_HOURS_DURATION 85 } 86 val startTime = max(curTime - showPermissionUsagesDuration, Instant.EPOCH.toEpochMilli()) 87 88 // Permission group to count mapping. 89 val usages: MutableMap<String, Int> = HashMap() 90 val permissionGroups: List<AppPermissionGroup> = getOSPermissionGroups(permissionUsages) 91 for (i in permissionGroups.indices) { 92 usages[permissionGroups[i].name] = 0 93 } 94 val permApps = ArrayList<PermissionApp>() 95 96 val exemptedPackages = Utils.getExemptedPackages(roleManager) 97 98 val seenSystemApp: Boolean = extractPermissionUsage(exemptedPackages, 99 usages, permApps, startTime, permissionUsages, showSystem) 100 101 return Triple(usages, permApps, seenSystemApp) 102 } 103 104 fun createGroupUsagesList( 105 context: Context, 106 usages: Map<String, Int> 107 ): List<Map.Entry<String, Int>> { 108 val groupUsageNameToLabel: MutableMap<String, CharSequence> = HashMap() 109 val groupUsagesList: MutableList<Map.Entry<String, Int>> = ArrayList(usages.entries) 110 val usagesEntryCount = groupUsagesList.size 111 for (usageEntryIndex in 0 until usagesEntryCount) { 112 val (key) = groupUsagesList[usageEntryIndex] 113 groupUsageNameToLabel[key] = getPermGroupLabel(context, key) 114 } 115 116 groupUsagesList.sortWith { e1: Map.Entry<String, Int>, e2: Map.Entry<String, Int> -> 117 comparePermissionGroupUsage(e1, e2, groupUsageNameToLabel) 118 } 119 120 return groupUsagesList 121 } 122 123 private fun comparePermissionGroupUsage( 124 first: Map.Entry<String, Int>, 125 second: Map.Entry<String, Int>, 126 groupUsageNameToLabelMapping: Map<String, CharSequence> 127 ): Int { 128 val firstPermissionOrder = PERMISSION_GROUP_ORDER 129 .getOrDefault(first.key, DEFAULT_ORDER) 130 val secondPermissionOrder = PERMISSION_GROUP_ORDER 131 .getOrDefault(second.key, DEFAULT_ORDER) 132 return if (firstPermissionOrder != secondPermissionOrder) { 133 firstPermissionOrder - secondPermissionOrder 134 } else groupUsageNameToLabelMapping[first.key].toString() 135 .compareTo(groupUsageNameToLabelMapping[second.key].toString()) 136 } 137 138 /** 139 * Get the permission groups declared by the OS. 140 * 141 * @return a list of the permission groups declared by the OS. 142 */ 143 private fun getOSPermissionGroups( 144 permissionUsages: List<AppPermissionUsage> 145 ): List<AppPermissionGroup> { 146 val groups: MutableList<AppPermissionGroup> = java.util.ArrayList() 147 val seenGroups: MutableSet<String> = ArraySet() 148 val numGroups: Int = permissionUsages.size 149 for (i in 0 until numGroups) { 150 val appUsage: AppPermissionUsage = permissionUsages.get(i) 151 val groupUsages = appUsage.groupUsages 152 val groupUsageCount = groupUsages.size 153 for (j in 0 until groupUsageCount) { 154 val groupUsage = groupUsages[j] 155 if (Utils.isModernPermissionGroup(groupUsage.group.name)) { 156 if (seenGroups.add(groupUsage.group.name)) { 157 groups.add(groupUsage.group) 158 } 159 } 160 } 161 } 162 return groups 163 } 164 165 /** 166 * Extract the permission usages from mAppPermissionUsages and put the extracted usages 167 * into usages and permApps. Returns whether we have seen a system app during the process. 168 * 169 * TODO: theianchen 170 * It's doing two things at the same method which is violating the SOLID principle. 171 * We should fix this. 172 * 173 * @param exemptedPackages packages that are the role holders for exempted roles 174 * @param usages an empty List that will be filled with permission usages. 175 * @param permApps an empty List that will be filled with permission apps. 176 * @return whether we have seen a system app. 177 */ 178 private fun extractPermissionUsage( 179 exemptedPackages: Set<String>, 180 usages: MutableMap<String, Int>, 181 permApps: java.util.ArrayList<PermissionApp>, 182 startTime: Long, 183 permissionUsages: List<AppPermissionUsage>, 184 showSystem: Boolean 185 ): Boolean { 186 187 val mGroupAppCounts: ArrayMap<String?, Int> = ArrayMap() 188 var seenSystemApp = false 189 val numApps: Int = permissionUsages.size 190 for (appNum in 0 until numApps) { 191 val appUsage: AppPermissionUsage = permissionUsages.get(appNum) 192 if (exemptedPackages.contains(appUsage.packageName)) { 193 continue 194 } 195 var used = false 196 val appGroups = appUsage.groupUsages 197 val numGroups = appGroups.size 198 for (groupNum in 0 until numGroups) { 199 val groupUsage = appGroups[groupNum] 200 val groupName = groupUsage.group.name 201 val lastAccessTime = groupUsage.lastAccessTime 202 if (lastAccessTime == 0L) { 203 Log.w( 204 LOG_TAG, 205 "Unexpected access time of 0 for ${appUsage.app.key} " + 206 groupUsage.group.name) 207 continue 208 } 209 if (lastAccessTime < startTime) { 210 continue 211 } 212 val isSystemApp = !Utils.isGroupOrBgGroupUserSensitive( 213 groupUsage.group) 214 seenSystemApp = seenSystemApp || isSystemApp 215 216 // If not showing system apps, skip. 217 if (!showSystem && isSystemApp) { 218 continue 219 } 220 used = true 221 addGroupUser(mGroupAppCounts, groupName) 222 usages[groupName] = usages.getOrDefault(groupName, 0) + 1 223 } 224 if (used) { 225 permApps.add(appUsage.app) 226 addGroupUser(mGroupAppCounts, null) 227 } 228 } 229 return seenSystemApp 230 } 231 232 private fun addGroupUser(groupAppCounts: ArrayMap<String?, Int>, app: String?) { 233 val count: Int? = groupAppCounts[app] 234 if (count == null) { 235 groupAppCounts[app] = 1 236 } else { 237 groupAppCounts[app] = count + 1 238 } 239 } 240 } 241 242 /** 243 * Factory for an PermissionUsageViewModel 244 */ 245 @RequiresApi(Build.VERSION_CODES.S) 246 class PermissionUsageViewModelFactory( 247 private val roleManager: RoleManager 248 ) : ViewModelProvider.Factory { 249 createnull250 override fun <T : ViewModel> create(modelClass: Class<T>): T { 251 @Suppress("UNCHECKED_CAST") 252 return PermissionUsageViewModel(roleManager) as T 253 } 254 } 255