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 @file:Suppress("DEPRECATION") 17 18 package com.android.permissioncontroller.permission.ui.model.v31 19 20 import android.Manifest 21 import android.app.AppOpsManager 22 import android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA 23 import android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE 24 import android.app.Application 25 import android.app.role.RoleManager 26 import android.content.ComponentName 27 import android.content.Context 28 import android.content.Intent 29 import android.content.pm.PackageManager 30 import android.content.res.Resources 31 import android.os.Build 32 import android.os.Bundle 33 import android.os.UserHandle 34 import androidx.annotation.RequiresApi 35 import androidx.lifecycle.AbstractSavedStateViewModelFactory 36 import androidx.lifecycle.SavedStateHandle 37 import androidx.lifecycle.ViewModel 38 import androidx.savedstate.SavedStateRegistryOwner 39 import com.android.modules.utils.build.SdkLevel 40 import com.android.permissioncontroller.PermissionControllerApplication 41 import com.android.permissioncontroller.R 42 import com.android.permissioncontroller.permission.compat.IntentCompat 43 import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData 44 import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData 45 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData 46 import com.android.permissioncontroller.permission.data.v31.AllLightHistoricalPackageOpsLiveData 47 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo 48 import com.android.permissioncontroller.permission.model.livedatatypes.v31.AppPermissionId 49 import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps 50 import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.AppPermissionDiscreteAccesses 51 import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.AttributedAppPermissionDiscreteAccesses 52 import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.Companion.NO_ATTRIBUTION_TAG 53 import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.DiscreteAccess 54 import com.android.permissioncontroller.permission.ui.handheld.v31.getDurationUsedStr 55 import com.android.permissioncontroller.permission.ui.handheld.v31.shouldShowSubattributionInPermissionsDashboard 56 import com.android.permissioncontroller.permission.utils.KotlinUtils 57 import com.android.permissioncontroller.permission.utils.KotlinUtils.getPackageLabel 58 import com.android.permissioncontroller.permission.utils.PermissionMapping 59 import com.android.permissioncontroller.permission.utils.Utils 60 import com.android.permissioncontroller.permission.utils.v31.SubattributionUtils 61 import java.time.Instant 62 import java.util.Objects 63 import java.util.concurrent.TimeUnit 64 import java.util.concurrent.TimeUnit.DAYS 65 66 /** [ViewModel] for the Permission Usage Details page. */ 67 @RequiresApi(Build.VERSION_CODES.S) 68 class PermissionUsageDetailsViewModel( 69 val application: Application, 70 private val state: SavedStateHandle, 71 private val permissionGroup: String, 72 ) : ViewModel() { 73 74 val allLightHistoricalPackageOpsLiveData = 75 AllLightHistoricalPackageOpsLiveData(application, opNames) 76 private val appPermGroupUiInfoLiveDataList = 77 mutableMapOf<AppPermissionId, AppPermGroupUiInfoLiveData>() 78 private val lightPackageInfoLiveDataMap = 79 mutableMapOf<Pair<String, UserHandle>, LightPackageInfoLiveData>() 80 val showSystemLiveData = state.getLiveData(SHOULD_SHOW_SYSTEM_KEY, false) 81 val show7DaysLiveData = state.getLiveData(SHOULD_SHOW_7_DAYS_KEY, false) 82 83 private val roleManager = 84 Utils.getSystemServiceSafe(application.applicationContext, RoleManager::class.java) 85 86 /** Updates whether system app permissions usage should be displayed in the UI. */ 87 fun updateShowSystemAppsToggle(showSystem: Boolean) { 88 if (showSystem != state[SHOULD_SHOW_SYSTEM_KEY]) { 89 state[SHOULD_SHOW_SYSTEM_KEY] = showSystem 90 } 91 } 92 93 /** Updates whether 7 days usage or 1 day usage should be displayed in the UI. */ 94 fun updateShow7DaysToggle(show7Days: Boolean) { 95 if (show7Days != state[SHOULD_SHOW_7_DAYS_KEY]) { 96 state[SHOULD_SHOW_7_DAYS_KEY] = show7Days 97 } 98 } 99 100 /** Creates a [PermissionUsageDetailsUiInfo] containing all information to render the UI. */ 101 fun buildPermissionUsageDetailsUiInfo(): PermissionUsageDetailsUiInfo { 102 val showSystem: Boolean = state[SHOULD_SHOW_SYSTEM_KEY] ?: false 103 val show7Days: Boolean = state[SHOULD_SHOW_7_DAYS_KEY] ?: false 104 val showPermissionUsagesDuration = 105 if (KotlinUtils.is7DayToggleEnabled() && show7Days) { 106 TIME_7_DAYS_DURATION 107 } else { 108 TIME_24_HOURS_DURATION 109 } 110 val startTime = 111 (System.currentTimeMillis() - showPermissionUsagesDuration).coerceAtLeast( 112 Instant.EPOCH.toEpochMilli()) 113 114 return PermissionUsageDetailsUiInfo( 115 show7Days, 116 showSystem, 117 buildAppPermissionAccessUiInfoList( 118 allLightHistoricalPackageOpsLiveData, startTime, showSystem), 119 containsSystemAppUsages(allLightHistoricalPackageOpsLiveData, startTime)) 120 } 121 122 /** 123 * Returns whether the "show/hide system" toggle should be displayed in the UI for the provided 124 * [AllLightHistoricalPackageOpsLiveData]. 125 */ 126 private fun containsSystemAppUsages( 127 allLightHistoricalPackageOpsLiveData: AllLightHistoricalPackageOpsLiveData, 128 startTime: Long 129 ): Boolean { 130 return allLightHistoricalPackageOpsLiveData 131 .getLightHistoricalPackageOps() 132 ?.flatMap { 133 it.appPermissionDiscreteAccesses 134 .map { it.withLabel() } 135 .filterOutExemptAppPermissions(true) 136 .filterAccessesLaterThan(startTime) 137 } 138 ?.any { isAppPermissionSystem(it.appPermissionId) } 139 ?: false 140 } 141 142 private fun isPermissionRequestedByApp(appPermissionId: AppPermissionId): Boolean { 143 val appRequestedPermissions = 144 lightPackageInfoLiveDataMap[ 145 Pair(appPermissionId.packageName, appPermissionId.userHandle)] 146 ?.value 147 ?.requestedPermissions 148 ?: listOf() 149 return appRequestedPermissions.any { 150 PermissionMapping.getGroupOfPlatformPermission(it) == appPermissionId.permissionGroup 151 } 152 } 153 154 private fun isAppPermissionSystem(appPermissionId: AppPermissionId): Boolean { 155 val appPermGroupUiInfo = appPermGroupUiInfoLiveDataList[appPermissionId]?.value 156 157 if (appPermGroupUiInfo != null) { 158 return appPermGroupUiInfo.isSystem 159 } else 160 // The AppPermGroupUiInfo may be null if it has either not loaded yet or if the app has not 161 // requested any permissions from the permission group in question. 162 // The Telecom doesn't request microphone or camera permissions. However, telecom app may 163 // use these permissions and they are considered system app permissions, so we return true 164 // even if the AppPermGroupUiInfo is unavailable. 165 if (appPermissionId.packageName == TELECOM_PACKAGE && 166 (appPermissionId.permissionGroup == Manifest.permission_group.CAMERA || 167 appPermissionId.permissionGroup == Manifest.permission_group.MICROPHONE)) { 168 return true 169 } 170 return false 171 } 172 173 /** 174 * Extracts access data from [AllLightHistoricalPackageOpsLiveData] and composes 175 * [AppPermissionAccessUiInfo]s to be displayed in the UI. 176 */ 177 private fun buildAppPermissionAccessUiInfoList( 178 allLightHistoricalPackageOpsLiveData: AllLightHistoricalPackageOpsLiveData, 179 startTime: Long, 180 showSystem: Boolean 181 ): List<AppPermissionAccessUiInfo> { 182 return allLightHistoricalPackageOpsLiveData 183 .getLightHistoricalPackageOps() 184 ?.flatMap { it.clusterAccesses(startTime, showSystem) } 185 ?.sortedBy { -1 * it.discreteAccesses.first().accessTimeMs } 186 ?.map { it.buildAppPermissionAccessUiInfo() } 187 ?: listOf() 188 } 189 190 private fun LightHistoricalPackageOps.clusterAccesses( 191 startTime: Long, 192 showSystem: Boolean 193 ): List<AppPermissionDiscreteAccessCluster> { 194 return if (!shouldShowSubAttributionForApp(getLightPackageInfo(packageName, userHandle))) 195 this.clusterAccessesWithoutAttribution(startTime, showSystem) 196 else { 197 this.clusterAccessesWithAttribution(startTime, showSystem) 198 } 199 } 200 201 /** 202 * Clusters accesses that are close enough together in time such that they can be displayed as a 203 * single access to the user. 204 * 205 * Accesses are clustered taking into account any app subattribution, so each cluster will 206 * pertain a particular attribution label. 207 */ 208 private fun LightHistoricalPackageOps.clusterAccessesWithAttribution( 209 startTime: Long, 210 showSystem: Boolean 211 ): List<AppPermissionDiscreteAccessCluster> = 212 this.attributedAppPermissionDiscreteAccesses 213 .flatMap { it.groupAccessesByLabel(getLightPackageInfo(packageName, userHandle)) } 214 .filterOutExemptAppPermissions(showSystem) 215 .filterAccessesLaterThan(startTime) 216 .flatMap { createAccessClusters(it) } 217 218 /** 219 * Clusters accesses that are close enough together in time such that they can be displayed as a 220 * single access to the user. 221 * 222 * Accesses are clustered disregarding any app subattribution. 223 */ 224 private fun LightHistoricalPackageOps.clusterAccessesWithoutAttribution( 225 startTime: Long, 226 showSystem: Boolean 227 ): List<AppPermissionDiscreteAccessCluster> = 228 this.appPermissionDiscreteAccesses 229 .map { it.withLabel() } 230 .filterOutExemptAppPermissions(showSystem) 231 .filterAccessesLaterThan(startTime) 232 .flatMap { createAccessClusters(it) } 233 234 /** Filters out accesses earlier than the provided start time. */ 235 private fun List<AppPermissionDiscreteAccessesWithLabel>.filterAccessesLaterThan( 236 startTime: Long, 237 ): List<AppPermissionDiscreteAccessesWithLabel> = 238 this.mapNotNull { 239 val updatedDiscreteAccesses = 240 it.discreteAccesses.filter { access -> access.accessTimeMs > startTime } 241 if (updatedDiscreteAccesses.isEmpty()) null 242 else 243 AppPermissionDiscreteAccessesWithLabel( 244 it.appPermissionId, 245 it.attributionLabel, 246 it.attributionTags, 247 updatedDiscreteAccesses) 248 } 249 250 /** Filters out data for apps and permissions that don't need to be displayed in the UI. */ 251 private fun List<AppPermissionDiscreteAccessesWithLabel>.filterOutExemptAppPermissions( 252 showSystem: Boolean 253 ): List<AppPermissionDiscreteAccessesWithLabel> { 254 return this.filter { 255 !Utils.getExemptedPackages(roleManager).contains(it.appPermissionId.packageName) 256 } 257 .filter { it.appPermissionId.permissionGroup == permissionGroup } 258 .filter { isPermissionRequestedByApp(it.appPermissionId) } 259 .filter { showSystem || !isAppPermissionSystem(it.appPermissionId) } 260 } 261 262 /** 263 * Converts the provided [AppPermissionDiscreteAccesses] to a 264 * [AppPermissionDiscreteAccessesWithLabel] by adding a label. 265 */ 266 private fun AppPermissionDiscreteAccesses.withLabel(): AppPermissionDiscreteAccessesWithLabel = 267 AppPermissionDiscreteAccessesWithLabel( 268 this.appPermissionId, 269 Resources.ID_NULL, 270 attributionTags = emptyList(), 271 this.discreteAccesses) 272 273 /** Groups tag-attributed accesses for the provided app and permission by attribution label. */ 274 private fun AttributedAppPermissionDiscreteAccesses.groupAccessesByLabel( 275 lightPackageInfo: LightPackageInfo? 276 ): List<AppPermissionDiscreteAccessesWithLabel> { 277 if (lightPackageInfo == null) return emptyList() 278 279 val appPermissionId = this.appPermissionId 280 val labelsToDiscreteAccesses = mutableMapOf<Int, MutableList<DiscreteAccess>>() 281 val labelsToTags = mutableMapOf<Int, MutableList<String>>() 282 283 val appPermissionDiscreteAccessWithLabels = 284 mutableListOf<AppPermissionDiscreteAccessesWithLabel>() 285 286 for ((tag, discreteAccesses) in this.attributedDiscreteAccesses) { 287 val label: Int = 288 if (tag == NO_ATTRIBUTION_TAG) Resources.ID_NULL 289 else lightPackageInfo.attributionTagsToLabels[tag] ?: Resources.ID_NULL 290 291 if (!labelsToDiscreteAccesses.containsKey(label)) { 292 labelsToDiscreteAccesses[label] = mutableListOf() 293 } 294 labelsToDiscreteAccesses[label]?.addAll(discreteAccesses) 295 296 if (!labelsToTags.containsKey(label)) { 297 labelsToTags[label] = mutableListOf() 298 } 299 labelsToTags[label]?.add(tag) 300 } 301 302 for ((label, discreteAccesses) in labelsToDiscreteAccesses.entries) { 303 val tags = labelsToTags[label]?.toList() ?: listOf() 304 305 appPermissionDiscreteAccessWithLabels.add( 306 AppPermissionDiscreteAccessesWithLabel( 307 appPermissionId, 308 label, 309 tags, 310 discreteAccesses.sortedBy { -1 * it.accessTimeMs })) 311 } 312 313 return appPermissionDiscreteAccessWithLabels 314 } 315 316 /** 317 * Clusters [DiscreteAccess]es represented by a [AppPermissionDiscreteAccessesWithLabel] into 318 * smaller groups to form a list of [AppPermissionDiscreteAccessCluster] instances. 319 * 320 * [DiscreteAccess]es which have accesses sufficiently close together in time will be places in 321 * the same cluster. 322 */ 323 private fun createAccessClusters( 324 appPermAccesses: AppPermissionDiscreteAccessesWithLabel, 325 ): List<AppPermissionDiscreteAccessCluster> { 326 val clusters = mutableListOf<AppPermissionDiscreteAccessCluster>() 327 val currentDiscreteAccesses = mutableListOf<DiscreteAccess>() 328 for (discreteAccess in appPermAccesses.discreteAccesses) { 329 if (currentDiscreteAccesses.isEmpty()) { 330 currentDiscreteAccesses.add(discreteAccess) 331 } else if (!canAccessBeAddedToCluster(discreteAccess, currentDiscreteAccesses)) { 332 clusters.add( 333 AppPermissionDiscreteAccessCluster( 334 appPermAccesses.appPermissionId, 335 appPermAccesses.attributionLabel, 336 appPermAccesses.attributionTags, 337 currentDiscreteAccesses.toMutableList())) 338 currentDiscreteAccesses.clear() 339 currentDiscreteAccesses.add(discreteAccess) 340 } else { 341 currentDiscreteAccesses.add(discreteAccess) 342 } 343 } 344 345 if (currentDiscreteAccesses.isNotEmpty()) { 346 clusters.add( 347 AppPermissionDiscreteAccessCluster( 348 appPermAccesses.appPermissionId, 349 appPermAccesses.attributionLabel, 350 appPermAccesses.attributionTags, 351 currentDiscreteAccesses.toMutableList())) 352 } 353 return clusters 354 } 355 356 /** 357 * Returns whether the provided [DiscreteAccess] occurred close enough to those in the clustered 358 * list that it can be added to the cluster. 359 */ 360 private fun canAccessBeAddedToCluster( 361 discreteAccess: DiscreteAccess, 362 clusteredAccesses: List<DiscreteAccess> 363 ): Boolean = 364 discreteAccess.accessTimeMs / ONE_HOUR_MS == 365 clusteredAccesses.first().accessTimeMs / ONE_HOUR_MS && 366 clusteredAccesses.last().accessTimeMs / ONE_MINUTE_MS - 367 discreteAccess.accessTimeMs / ONE_MINUTE_MS <= CLUSTER_SPACING_MINUTES 368 369 /** 370 * Composes all UI information from a [AppPermissionDiscreteAccessCluster] into a 371 * [AppPermissionAccessUiInfo]. 372 */ 373 private fun AppPermissionDiscreteAccessCluster.buildAppPermissionAccessUiInfo(): 374 AppPermissionAccessUiInfo { 375 val context = application 376 val accessTimeList = this.discreteAccesses.map { it.accessTimeMs } 377 val durationSummaryLabel = getDurationSummary(context, this, accessTimeList) 378 val proxyLabel = getProxyPackageLabel(this) 379 val subAttributionLabel = getSubAttributionLabel(this) 380 val showingSubAttribution = subAttributionLabel != null && subAttributionLabel.isNotEmpty() 381 val summary = 382 buildUsageSummary(context, subAttributionLabel, proxyLabel, durationSummaryLabel) 383 384 return AppPermissionAccessUiInfo( 385 this.appPermissionId.userHandle, 386 this.appPermissionId.packageName, 387 permissionGroup, 388 this.discreteAccesses.last().accessTimeMs, 389 this.discreteAccesses.first().accessTimeMs, 390 summary, 391 showingSubAttribution, 392 ArrayList(this.attributionTags)) 393 } 394 395 /** Builds a summary of the permission access. */ 396 private fun buildUsageSummary( 397 context: Context, 398 subAttributionLabel: String?, 399 proxyPackageLabel: String?, 400 durationSummary: String? 401 ): String? { 402 val subTextStrings: MutableList<String> = mutableListOf() 403 404 subAttributionLabel?.let { subTextStrings.add(subAttributionLabel) } 405 proxyPackageLabel?.let { subTextStrings.add(it) } 406 durationSummary?.let { subTextStrings.add(it) } 407 return when (subTextStrings.size) { 408 3 -> 409 context.getString( 410 R.string.history_preference_subtext_3, 411 subTextStrings[0], 412 subTextStrings[1], 413 subTextStrings[2]) 414 2 -> 415 context.getString( 416 R.string.history_preference_subtext_2, subTextStrings[0], subTextStrings[1]) 417 1 -> subTextStrings[0] 418 else -> null 419 } 420 } 421 422 /** Returns whether app subattribution should be shown. */ 423 private fun shouldShowSubAttributionForApp(lightPackageInfo: LightPackageInfo?): Boolean { 424 return lightPackageInfo != null && 425 shouldShowSubattributionInPermissionsDashboard() && 426 SubattributionUtils.isSubattributionSupported(lightPackageInfo) 427 } 428 429 /** Returns a summary of the duration the permission was accessed for. */ 430 private fun getDurationSummary( 431 context: Context, 432 accessCluster: AppPermissionDiscreteAccessCluster, 433 accessTimeList: List<Long>, 434 ): String? { 435 if (accessTimeList.isEmpty()) { 436 return null 437 } 438 // Since Location accesses are atomic, we manually calculate the access duration by 439 // comparing the first and last access within the cluster. 440 val durationMs: Long = 441 if (permissionGroup == Manifest.permission_group.LOCATION) { 442 accessTimeList[0] - accessTimeList[accessTimeList.size - 1] 443 } else { 444 accessCluster.discreteAccesses 445 .filter { it.accessDurationMs > 0 } 446 .sumOf { it.accessDurationMs } 447 } 448 449 // Only show the duration summary if it is at least (CLUSTER_SPACING_MINUTES + 1) minutes. 450 // Displaying a time that is shorter than the cluster granularity 451 // (CLUSTER_SPACING_MINUTES) will not convey useful information. 452 if (durationMs >= TimeUnit.MINUTES.toMillis(CLUSTER_SPACING_MINUTES + 1)) { 453 return getDurationUsedStr(context, durationMs) 454 } 455 456 return null 457 } 458 459 /** Returns the proxied package label if the permission access was proxied. */ 460 private fun getProxyPackageLabel(accessCluster: AppPermissionDiscreteAccessCluster): String? = 461 accessCluster.discreteAccesses 462 .firstOrNull { it.proxy?.packageName != null } 463 ?.let { 464 getPackageLabel( 465 PermissionControllerApplication.get(), 466 it.proxy!!.packageName!!, 467 UserHandle.getUserHandleForUid(it.proxy.uid)) 468 } 469 470 /** Returns the attribution label for the permission access, if any. */ 471 private fun getSubAttributionLabel(accessCluster: AppPermissionDiscreteAccessCluster): String? = 472 if (accessCluster.attributionLabel == Resources.ID_NULL) null 473 else { 474 val lightPackageInfo = getLightPackageInfo(accessCluster.appPermissionId) 475 getSubAttributionLabels(lightPackageInfo)?.get(accessCluster.attributionLabel) 476 } 477 478 private fun getSubAttributionLabels(lightPackageInfo: LightPackageInfo?): Map<Int, String>? = 479 if (lightPackageInfo == null) null 480 else SubattributionUtils.getAttributionLabels(application, lightPackageInfo) 481 482 private fun getLightPackageInfo(appPermissionId: AppPermissionId) = 483 lightPackageInfoLiveDataMap[Pair(appPermissionId.packageName, appPermissionId.userHandle)] 484 ?.value 485 486 private fun getLightPackageInfo(packageName: String, userHandle: UserHandle) = 487 lightPackageInfoLiveDataMap[Pair(packageName, userHandle)]?.value 488 489 private fun AllLightHistoricalPackageOpsLiveData.getLightHistoricalPackageOps() = 490 this.value?.values 491 492 /** Data used to create a preference for an app's permission usage. */ 493 data class AppPermissionAccessUiInfo( 494 val userHandle: UserHandle, 495 val pkgName: String, 496 val permissionGroup: String, 497 val accessStartTime: Long, 498 val accessEndTime: Long, 499 val summaryText: CharSequence?, 500 val showingAttribution: Boolean, 501 val attributionTags: ArrayList<String>, 502 ) 503 504 /** 505 * Class containing all the information needed by the permission usage details fragments to 506 * render UI. 507 */ 508 data class PermissionUsageDetailsUiInfo( 509 /** 510 * Whether to show data over the last 7 days. 511 * 512 * While this information is available from the [SHOULD_SHOW_7_DAYS_KEY] state, we include 513 * it in the UI info so that it triggers a UI update when changed. 514 */ 515 private val show7Days: Boolean, 516 /** 517 * Whether to show system apps' data. 518 * 519 * While this information is available from the [SHOULD_SHOW_SYSTEM_KEY] state, we include 520 * it in the UI info so that it triggers a UI update when changed. 521 */ 522 private val showSystem: Boolean, 523 /** List of [AppPermissionAccessUiInfo]s to be displayed in the UI. */ 524 val appPermissionAccessUiInfoList: List<AppPermissionAccessUiInfo>, 525 /** Whether to show the "show/hide system" toggle. */ 526 val containsSystemAppAccesses: Boolean, 527 ) 528 529 /** 530 * Data class representing a cluster of permission accesses close enough together to be 531 * displayed as a single access in the UI. 532 */ 533 private data class AppPermissionDiscreteAccessCluster( 534 val appPermissionId: AppPermissionId, 535 val attributionLabel: Int, 536 val attributionTags: List<String>, 537 val discreteAccesses: List<DiscreteAccess>, 538 ) 539 540 /** 541 * Data class representing all permission accesses for a particular package, user, permission 542 * and attribution label. 543 */ 544 private data class AppPermissionDiscreteAccessesWithLabel( 545 val appPermissionId: AppPermissionId, 546 val attributionLabel: Int, 547 val attributionTags: List<String>, 548 val discreteAccesses: List<DiscreteAccess> 549 ) 550 551 /** [LiveData] object for [PermissionUsageDetailsUiInfo]. */ 552 val permissionUsagesDetailsInfoUiLiveData = 553 object : SmartUpdateMediatorLiveData<@JvmSuppressWildcards PermissionUsageDetailsUiInfo>() { 554 private val getAppPermGroupUiInfoLiveData = { appPermissionId: AppPermissionId -> 555 AppPermGroupUiInfoLiveData[ 556 Triple( 557 appPermissionId.packageName, 558 appPermissionId.permissionGroup, 559 appPermissionId.userHandle, 560 )] 561 } 562 private val getLightPackageInfoLiveData = 563 { packageWithUserHandle: Pair<String, UserHandle> -> 564 LightPackageInfoLiveData[packageWithUserHandle] 565 } 566 567 init { 568 addSource(allLightHistoricalPackageOpsLiveData) { update() } 569 addSource(showSystemLiveData) { update() } 570 addSource(show7DaysLiveData) { update() } 571 } 572 573 override fun onUpdate() { 574 if (!allLightHistoricalPackageOpsLiveData.isInitialized) { 575 return 576 } 577 578 val appPermissionIds = mutableSetOf<AppPermissionId>() 579 val allPackages: Set<Pair<String, UserHandle>> = 580 allLightHistoricalPackageOpsLiveData.value?.keys ?: setOf() 581 for (packageWithUserHandle: Pair<String, UserHandle> in allPackages) { 582 val appPermGroupIds = 583 allLightHistoricalPackageOpsLiveData.value 584 ?.get(packageWithUserHandle) 585 ?.appPermissionDiscreteAccesses 586 ?.map { it.appPermissionId } 587 ?.toSet() 588 ?: setOf() 589 590 appPermissionIds.addAll(appPermGroupIds) 591 } 592 593 setSourcesToDifference( 594 appPermissionIds, 595 appPermGroupUiInfoLiveDataList, 596 getAppPermGroupUiInfoLiveData) { 597 update() 598 } 599 setSourcesToDifference( 600 allPackages, lightPackageInfoLiveDataMap, getLightPackageInfoLiveData) { 601 update() 602 } 603 604 if (appPermGroupUiInfoLiveDataList.any { it.value.isStale }) { 605 return 606 } 607 608 if (lightPackageInfoLiveDataMap.any { it.value.isStale }) { 609 return 610 } 611 612 value = buildPermissionUsageDetailsUiInfo() 613 } 614 } 615 616 /** Companion object for [PermissionUsageDetailsViewModel]. */ 617 companion object { 618 private const val ONE_HOUR_MS = 3_600_000 619 private const val ONE_MINUTE_MS = 60_000 620 private const val CLUSTER_SPACING_MINUTES: Long = 1L 621 private const val TELECOM_PACKAGE = "com.android.server.telecom" 622 private val TIME_7_DAYS_DURATION: Long = DAYS.toMillis(7) 623 private val TIME_24_HOURS_DURATION: Long = DAYS.toMillis(1) 624 internal const val SHOULD_SHOW_SYSTEM_KEY = "showSystem" 625 internal const val SHOULD_SHOW_7_DAYS_KEY = "show7Days" 626 627 /** Returns all op names for all permissions in a list of permission groups. */ 628 val opNames = 629 listOf( 630 Manifest.permission_group.CAMERA, 631 Manifest.permission_group.LOCATION, 632 Manifest.permission_group.MICROPHONE) 633 .flatMap { group -> PermissionMapping.getPlatformPermissionNamesOfGroup(group) } 634 .mapNotNull { permName -> AppOpsManager.permissionToOp(permName) } 635 .toMutableSet() 636 .apply { 637 add(OPSTR_PHONE_CALL_MICROPHONE) 638 add(OPSTR_PHONE_CALL_CAMERA) 639 if (SdkLevel.isAtLeastT()) { 640 add(AppOpsManager.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO) 641 } 642 } 643 644 /** Creates the [Intent] for the click action of a privacy dashboard app usage event. */ 645 fun createHistoryPreferenceClickIntent( 646 context: Context, 647 userHandle: UserHandle, 648 packageName: String, 649 permissionGroup: String, 650 accessStartTime: Long, 651 accessEndTime: Long, 652 showingAttribution: Boolean, 653 attributionTags: List<String> 654 ): Intent { 655 return getManagePermissionUsageIntent( 656 context, 657 packageName, 658 permissionGroup, 659 accessStartTime, 660 accessEndTime, 661 showingAttribution, 662 attributionTags) 663 ?: getDefaultManageAppPermissionsIntent(packageName, userHandle) 664 } 665 666 /** 667 * Gets an [Intent.ACTION_MANAGE_PERMISSION_USAGE] intent, or null if attribution shouldn't 668 * be shown or the intent can't be handled. 669 */ 670 private fun getManagePermissionUsageIntent( 671 context: Context, 672 packageName: String, 673 permissionGroup: String, 674 accessStartTime: Long, 675 accessEndTime: Long, 676 showingAttribution: Boolean, 677 attributionTags: List<String> 678 ): Intent? { 679 // TODO(b/255992934) only location provider apps should be able to provide this intent 680 if (!showingAttribution || !SdkLevel.isAtLeastT()) { 681 return null 682 } 683 val intent = 684 Intent(Intent.ACTION_MANAGE_PERMISSION_USAGE).apply { 685 setPackage(packageName) 686 putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permissionGroup) 687 putExtra(Intent.EXTRA_ATTRIBUTION_TAGS, attributionTags.toTypedArray()) 688 putExtra(Intent.EXTRA_START_TIME, accessStartTime) 689 putExtra(Intent.EXTRA_END_TIME, accessEndTime) 690 putExtra(IntentCompat.EXTRA_SHOWING_ATTRIBUTION, showingAttribution) 691 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 692 } 693 val resolveInfo = 694 context.packageManager.resolveActivity( 695 intent, PackageManager.ResolveInfoFlags.of(0)) 696 if (resolveInfo?.activityInfo == null || 697 !Objects.equals( 698 resolveInfo.activityInfo.permission, 699 Manifest.permission.START_VIEW_PERMISSION_USAGE)) { 700 return null 701 } 702 intent.component = ComponentName(packageName, resolveInfo.activityInfo.name) 703 return intent 704 } 705 706 private fun getDefaultManageAppPermissionsIntent( 707 packageName: String, 708 userHandle: UserHandle 709 ): Intent { 710 return Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS).apply { 711 putExtra(Intent.EXTRA_USER, userHandle) 712 putExtra(Intent.EXTRA_PACKAGE_NAME, packageName) 713 } 714 } 715 } 716 717 /** Factory for [PermissionUsageDetailsViewModel]. */ 718 @RequiresApi(Build.VERSION_CODES.S) 719 class PermissionUsageDetailsViewModelFactory( 720 val app: Application, 721 owner: SavedStateRegistryOwner, 722 private val permissionGroup: String, 723 ) : AbstractSavedStateViewModelFactory(owner, Bundle()) { 724 override fun <T : ViewModel> create( 725 key: String, 726 modelClass: Class<T>, 727 handle: SavedStateHandle, 728 ): T { 729 @Suppress("UNCHECKED_CAST") 730 return PermissionUsageDetailsViewModel(app, handle, permissionGroup) as T 731 } 732 } 733 } 734