1 /* <lambda>null2 * Copyright (C) 2020 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.Manifest.permission_group.CAMERA 22 import android.Manifest.permission_group.LOCATION 23 import android.Manifest.permission_group.MICROPHONE 24 import android.content.ComponentName 25 import android.content.Context 26 import android.content.Intent 27 import android.content.pm.PackageManager 28 import android.media.AudioManager 29 import android.media.AudioManager.MODE_IN_COMMUNICATION 30 import android.os.Bundle 31 import android.os.UserHandle 32 import android.provider.Settings 33 import android.speech.RecognitionService 34 import android.speech.RecognizerIntent 35 import android.telephony.TelephonyManager 36 import android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS 37 import android.view.inputmethod.InputMethodManager 38 import androidx.lifecycle.AbstractSavedStateViewModelFactory 39 import androidx.lifecycle.SavedStateHandle 40 import androidx.lifecycle.ViewModel 41 import androidx.savedstate.SavedStateRegistryOwner 42 import com.android.permissioncontroller.PermissionControllerApplication 43 import com.android.permissioncontroller.permission.data.AttributionLabelLiveData 44 import com.android.permissioncontroller.permission.data.LoadAndFreezeLifeData 45 import com.android.permissioncontroller.permission.data.OpAccess 46 import com.android.permissioncontroller.permission.data.OpUsageLiveData 47 import com.android.permissioncontroller.permission.data.PermGroupUsageLiveData 48 import com.android.permissioncontroller.permission.data.SmartAsyncMediatorLiveData 49 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData 50 import com.android.permissioncontroller.permission.data.micMutedLiveData 51 import com.android.permissioncontroller.permission.ui.handheld.v31.ReviewOngoingUsageFragment.PHONE_CALL 52 import com.android.permissioncontroller.permission.ui.handheld.v31.ReviewOngoingUsageFragment.VIDEO_CALL 53 import com.android.permissioncontroller.permission.utils.KotlinUtils 54 import com.android.permissioncontroller.permission.utils.KotlinUtils.shouldShowLocationIndicators 55 import com.android.permissioncontroller.permission.utils.Utils 56 import java.time.Instant 57 import kotlin.math.max 58 import kotlinx.coroutines.Job 59 60 private const val FIRST_OPENED_KEY = "FIRST_OPENED" 61 private const val CALL_OP_USAGE_KEY = "CALL_OP_USAGE" 62 private const val USAGES_KEY = "USAGES_KEY" 63 private const val MIC_MUTED_KEY = "MIC_MUTED_KEY" 64 65 /** ViewModel for {@link ReviewOngoingUsageFragment} */ 66 class ReviewOngoingUsageViewModel(state: SavedStateHandle, extraDurationMills: Long) : ViewModel() { 67 /** Time of oldest usages considered */ 68 private val startTime = 69 max(state.get<Long>(FIRST_OPENED_KEY)!! - extraDurationMills, Instant.EPOCH.toEpochMilli()) 70 71 private val SYSTEM_PKG = "android" 72 73 data class Usages( 74 /** attribution-res-id/packageName/user -> perm groups accessed */ 75 val appUsages: Map<PackageAttribution, Set<String>>, 76 /** Op-names of phone call accesses */ 77 val callUsages: Collection<String>, 78 /** 79 * A map of attribution, packageName and user -> list of attribution labels to show with 80 * microphone 81 */ 82 val shownAttributions: Map<PackageAttribution, List<CharSequence>> = emptyMap() 83 ) 84 85 data class PackageAttribution( 86 val attributionTag: String?, 87 val packageName: String, 88 val user: UserHandle 89 ) { 90 fun pkgEq(other: PackageAttribution): Boolean { 91 return packageName == other.packageName && user == other.user 92 } 93 } 94 95 /** 96 * Base permission usage that will filtered by SystemPermGroupUsages and 97 * UserSensitivePermGroupUsages. 98 * 99 * <p>Note: This does not use a cached live-data to avoid getting stale data 100 */ 101 private val permGroupUsages = 102 LoadAndFreezeLifeData( 103 state, 104 USAGES_KEY, 105 PermGroupUsageLiveData( 106 PermissionControllerApplication.get(), 107 if (shouldShowLocationIndicators()) { 108 listOf(CAMERA, LOCATION, MICROPHONE) 109 } else { 110 listOf(CAMERA, MICROPHONE) 111 }, 112 System.currentTimeMillis() - startTime 113 ) 114 ) 115 116 /** Whether the mic is muted */ 117 private val isMicMuted = LoadAndFreezeLifeData(state, MIC_MUTED_KEY, micMutedLiveData) 118 119 /** App runtime permission usages */ 120 private val appUsagesLiveData = 121 object : SmartUpdateMediatorLiveData<Map<PackageAttribution, Set<String>>>() { 122 private val app = PermissionControllerApplication.get() 123 124 init { 125 addSource(permGroupUsages) { update() } 126 127 addSource(isMicMuted) { update() } 128 } 129 130 override fun onUpdate() { 131 if (!permGroupUsages.isInitialized || !isMicMuted.isInitialized) { 132 return 133 } 134 135 if (permGroupUsages.value == null) { 136 value = null 137 return 138 } 139 140 // Filter out system package 141 val filteredUsages = mutableMapOf<PackageAttribution, MutableSet<String>>() 142 for ((permGroupName, usages) in permGroupUsages.value!!) { 143 if (permGroupName == MICROPHONE && isMicMuted.value == true) { 144 continue 145 } 146 147 for (usage in usages) { 148 if (usage.packageName != SYSTEM_PKG) { 149 filteredUsages 150 .getOrPut(getPackageAttr(usage), { mutableSetOf() }) 151 .add(permGroupName) 152 } 153 } 154 } 155 156 value = filteredUsages 157 } 158 159 // TODO ntmyren: Replace this with better check if this moves beyond teamfood 160 private fun isAppPredictor(usage: OpAccess): Boolean { 161 return Utils.getUserContext(app, usage.user) 162 .packageManager 163 .checkPermission( 164 Manifest.permission.MANAGE_APP_PREDICTIONS, 165 usage.packageName 166 ) == PackageManager.PERMISSION_GRANTED 167 } 168 } 169 170 /** 171 * Gets all trusted proxied voice IME and voice recognition microphone uses, and get the label 172 * needed to display with it, as well as information about the proxy whose label is being shown, 173 * if applicable. 174 */ 175 private val trustedAttrsLiveData = 176 object : SmartAsyncMediatorLiveData<Map<PackageAttribution, CharSequence>>() { 177 private val VOICE_IME_SUBTYPE = "voice" 178 179 private val attributionLabelLiveDatas = 180 mutableMapOf<Triple<String?, String, UserHandle>, AttributionLabelLiveData>() 181 182 init { 183 addSource(permGroupUsages) { updateAsync() } 184 } 185 186 override suspend fun loadDataAndPostValue(job: Job) { 187 if (!permGroupUsages.isInitialized) { 188 return 189 } 190 val usages = 191 permGroupUsages.value?.get(MICROPHONE) 192 ?: run { 193 postValue(emptyMap()) 194 return 195 } 196 val proxies = usages.mapNotNull { it.proxyAccess } 197 198 val proxyLabelLiveDatas = 199 proxies.map { Triple(it.attributionTag, it.packageName, it.user) } 200 val toAddLabelLiveDatas = 201 (usages.map { Triple(it.attributionTag, it.packageName, it.user) } + 202 proxyLabelLiveDatas) 203 .distinct() 204 val getLiveDataFun = { key: Triple<String?, String, UserHandle> -> 205 AttributionLabelLiveData[key] 206 } 207 setSourcesToDifference( 208 toAddLabelLiveDatas, 209 attributionLabelLiveDatas, 210 getLiveDataFun 211 ) 212 213 if (attributionLabelLiveDatas.any { !it.value.isInitialized }) { 214 return 215 } 216 217 val approvedAttrs = mutableMapOf<PackageAttribution, String>() 218 for (user in usages.map { it.user }.distinct()) { 219 val userContext = 220 Utils.getUserContext(PermissionControllerApplication.get(), user) 221 222 // TODO ntmyren: Observe changes, possibly split into separate LiveDatas 223 val voiceInputs = mutableMapOf<String, CharSequence>() 224 userContext 225 .getSystemService(InputMethodManager::class.java)!! 226 .enabledInputMethodList 227 .forEach { 228 for (i in 0 until it.subtypeCount) { 229 if (it.getSubtypeAt(i).mode == VOICE_IME_SUBTYPE) { 230 voiceInputs[it.packageName] = 231 it.serviceInfo.loadSafeLabel( 232 userContext.packageManager, 233 Float.MAX_VALUE, 234 0 235 ) 236 break 237 } 238 } 239 } 240 241 // Get the currently selected recognizer from the secure setting. 242 val recognitionPackageName = 243 Settings.Secure.getString( 244 userContext.contentResolver, 245 // Settings.Secure.VOICE_RECOGNITION_SERVICE 246 "voice_recognition_service" 247 ) 248 ?.let(ComponentName::unflattenFromString) 249 ?.packageName 250 251 val recognizers = mutableMapOf<String, CharSequence>() 252 val availableRecognizers = 253 userContext.packageManager.queryIntentServices( 254 Intent(RecognitionService.SERVICE_INTERFACE), 255 PackageManager.GET_META_DATA 256 ) 257 availableRecognizers.forEach { 258 val sI = it.serviceInfo 259 if (sI.packageName == recognitionPackageName) { 260 recognizers[sI.packageName] = 261 sI.loadSafeLabel(userContext.packageManager, Float.MAX_VALUE, 0) 262 } 263 } 264 265 val recognizerIntents = mutableMapOf<String, CharSequence>() 266 val availableRecognizerIntents = 267 userContext.packageManager.queryIntentActivities( 268 Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 269 PackageManager.GET_META_DATA 270 ) 271 availableRecognizers.forEach { rI -> 272 val servicePkg = rI.serviceInfo.packageName 273 if ( 274 servicePkg == recognitionPackageName && 275 availableRecognizerIntents.any { 276 it.activityInfo.packageName == servicePkg 277 } 278 ) { 279 // If this recognizer intent is also a recognizer service, and is 280 // trusted, 281 // Then attribute to voice recognition 282 recognizerIntents[servicePkg] = 283 rI.serviceInfo.loadSafeLabel( 284 userContext.packageManager, 285 Float.MAX_VALUE, 286 0 287 ) 288 } 289 } 290 291 // get attribution labels for voice IME, recognition intents, and recognition 292 // services 293 for (opAccess in usages) { 294 setTrustedAttrsForAccess( 295 userContext, 296 opAccess, 297 user, 298 false, 299 voiceInputs, 300 approvedAttrs 301 ) 302 setTrustedAttrsForAccess( 303 userContext, 304 opAccess, 305 user, 306 false, 307 recognizerIntents, 308 approvedAttrs 309 ) 310 setTrustedAttrsForAccess( 311 userContext, 312 opAccess, 313 user, 314 true, 315 recognizers, 316 approvedAttrs 317 ) 318 } 319 } 320 postValue(approvedAttrs) 321 } 322 323 private fun setTrustedAttrsForAccess( 324 context: Context, 325 opAccess: OpAccess, 326 currUser: UserHandle, 327 getProxyLabel: Boolean, 328 trustedMap: Map<String, CharSequence>, 329 toSetMap: MutableMap<PackageAttribution, String> 330 ) { 331 val access = 332 if (getProxyLabel) { 333 opAccess.proxyAccess 334 } else { 335 opAccess 336 } 337 338 if ( 339 access == null || access.user != currUser || access.packageName !in trustedMap 340 ) { 341 return 342 } 343 344 val appAttr = getPackageAttr(access) 345 val packageName = access.packageName 346 347 val labelResId = 348 attributionLabelLiveDatas[ 349 Triple(access.attributionTag, access.packageName, access.user)] 350 ?.value 351 ?: 0 352 val label = 353 try { 354 context.createPackageContext(packageName, 0).getString(labelResId) 355 } catch (e: Exception) { 356 return 357 } 358 if (trustedMap[packageName] == label) { 359 toSetMap[appAttr] = label 360 } 361 } 362 } 363 364 /** 365 * Get all chains of proxy usages. A proxy chain is defined as one usage at the root, then 366 * further proxy usages, where the app and attribution tag of the proxy matches the previous 367 * usage in the chain. 368 */ 369 private val proxyChainsLiveData = 370 object : SmartUpdateMediatorLiveData<Set<List<OpAccess>>>() { 371 init { 372 addSource(permGroupUsages) { update() } 373 } 374 override fun onUpdate() { 375 if (!permGroupUsages.isInitialized) { 376 return 377 } 378 val usages = permGroupUsages.value?.get(MICROPHONE) ?: emptyList() 379 // a map of chain start -> in progress chain 380 val proxyChains = mutableMapOf<PackageAttribution, MutableList<OpAccess>>() 381 382 val remainingProxyChainUsages = mutableMapOf<PackageAttribution, OpAccess>() 383 for (usage in usages) { 384 remainingProxyChainUsages[getPackageAttr(usage)] = usage 385 } 386 // find all one-link chains (that is, all proxied apps whose proxy is not included 387 // in 388 // the usage list) 389 for (usage in usages) { 390 val usageAttr = getPackageAttr(usage) 391 val proxyAttr = getPackageAttr(usage.proxyAccess ?: continue) 392 if (!usages.any { getPackageAttr(it) == proxyAttr }) { 393 proxyChains[usageAttr] = mutableListOf(usage) 394 remainingProxyChainUsages.remove(usageAttr) 395 } 396 } 397 398 // find all possible starting points for chains 399 for ((usageAttr, usage) in remainingProxyChainUsages.toMap()) { 400 // If this usage has a proxy, but is not a proxy, it is the start of a chain. 401 // If it has no proxy, and isn't a proxy, remove it. 402 if ( 403 !remainingProxyChainUsages.values.any { 404 it.proxyAccess != null && getPackageAttr(it.proxyAccess) == usageAttr 405 } 406 ) { 407 if (usage.proxyAccess != null) { 408 proxyChains[usageAttr] = mutableListOf(usage) 409 } else { 410 remainingProxyChainUsages.remove(usageAttr) 411 } 412 } 413 } 414 415 // assemble the chains 416 for ((startUsageAttr, proxyChain) in proxyChains) { 417 var currentUsage = remainingProxyChainUsages[startUsageAttr] ?: continue 418 while (currentUsage.proxyAccess != null) { 419 val currPackageAttr = getPackageAttr(currentUsage.proxyAccess!!) 420 currentUsage = remainingProxyChainUsages[currPackageAttr] ?: break 421 if (proxyChain.any { it == currentUsage }) { 422 // we have a cycle, and should break 423 break 424 } 425 proxyChain.add(currentUsage) 426 } 427 // invert the lists, so the element without a proxy is first on the list 428 proxyChain.reverse() 429 } 430 431 value = proxyChains.values.toSet() 432 } 433 } 434 435 /** Phone call usages */ 436 private val callOpUsageLiveData = 437 object : SmartUpdateMediatorLiveData<Collection<String>>() { 438 private val rawOps = 439 LoadAndFreezeLifeData( 440 state, 441 CALL_OP_USAGE_KEY, 442 OpUsageLiveData[ 443 listOf(PHONE_CALL, VIDEO_CALL), System.currentTimeMillis() - startTime] 444 ) 445 446 init { 447 addSource(rawOps) { update() } 448 449 addSource(isMicMuted) { update() } 450 } 451 452 override fun onUpdate() { 453 if (!isMicMuted.isInitialized || !rawOps.isInitialized) { 454 return 455 } 456 457 value = 458 if (isMicMuted.value == true) { 459 rawOps.value!!.keys.filter { it != PHONE_CALL } 460 } else { 461 rawOps.value!!.keys 462 } 463 } 464 } 465 466 /** App, system, and call usages in a single, nice, handy package */ 467 val usages = 468 object : SmartAsyncMediatorLiveData<Usages>() { 469 private val app = PermissionControllerApplication.get() 470 471 init { 472 addSource(appUsagesLiveData) { update() } 473 474 addSource(callOpUsageLiveData) { update() } 475 476 addSource(trustedAttrsLiveData) { update() } 477 478 addSource(proxyChainsLiveData) { update() } 479 } 480 481 override suspend fun loadDataAndPostValue(job: Job) { 482 if (job.isCancelled) { 483 return 484 } 485 486 if ( 487 !callOpUsageLiveData.isInitialized || 488 !appUsagesLiveData.isInitialized || 489 !trustedAttrsLiveData.isInitialized || 490 !proxyChainsLiveData.isInitialized 491 ) { 492 return 493 } 494 495 val callOpUsages = callOpUsageLiveData.value?.toMutableSet() 496 val appUsages = appUsagesLiveData.value?.toMutableMap() 497 val approvedAttrs = trustedAttrsLiveData.value?.toMutableMap() ?: mutableMapOf() 498 val proxyChains = proxyChainsLiveData.value ?: emptySet() 499 500 if (callOpUsages == null || appUsages == null) { 501 postValue(null) 502 return 503 } 504 505 // If there is nothing to show the dialog should be closed, hence return a "invalid" 506 // value 507 if (appUsages.isEmpty() && callOpUsages.isEmpty()) { 508 postValue(null) 509 return 510 } 511 512 // If we are in a VOIP call (aka MODE_IN_COMMUNICATION), and have a carrier 513 // privileged 514 // app using the mic, hide phone usage. 515 val audioManager = app.getSystemService(AudioManager::class.java)!! 516 if (callOpUsages.isNotEmpty() && audioManager.mode == MODE_IN_COMMUNICATION) { 517 val telephonyManager = app.getSystemService(TelephonyManager::class.java)!! 518 for ((pkg, usages) in appUsages) { 519 if ( 520 telephonyManager.checkCarrierPrivilegesForPackage(pkg.packageName) == 521 CARRIER_PRIVILEGE_STATUS_HAS_ACCESS && usages.contains(MICROPHONE) 522 ) { 523 callOpUsages.clear() 524 continue 525 } 526 } 527 } 528 529 // Find labels for proxies, and assign them to the proper app, removing other usages 530 val approvedLabels = mutableMapOf<PackageAttribution, List<CharSequence>>() 531 for (chain in proxyChains) { 532 // if the final link in the chain is not user sensitive, do not show the chain 533 if (getPackageAttr(chain[chain.size - 1]) !in appUsages) { 534 continue 535 } 536 537 // if the proxy access is missing, for some reason, do not show the proxy 538 if (chain.size == 1) { 539 continue 540 } 541 542 val labels = mutableListOf<CharSequence>() 543 for ((idx, opAccess) in chain.withIndex()) { 544 val appAttr = getPackageAttr(opAccess) 545 // If this is the last link in the proxy chain, assign it the series of 546 // labels 547 // Else, if it has a special label, add that label 548 // Else, if there are no other apps in the remaining part of the chain which 549 // have the same package name, add the app label 550 // If it is not the last link in the chain, remove its attribution 551 if (idx == chain.size - 1) { 552 approvedLabels[appAttr] = labels 553 continue 554 } else if (appAttr in approvedAttrs) { 555 labels.add(approvedAttrs[appAttr]!!) 556 approvedAttrs.remove(appAttr) 557 } else if ( 558 chain.subList(idx + 1, chain.size).all { 559 it.packageName != opAccess.packageName 560 } && opAccess.packageName != SYSTEM_PKG 561 ) { 562 labels.add( 563 KotlinUtils.getPackageLabel( 564 app, 565 opAccess.packageName, 566 opAccess.user 567 ) 568 ) 569 } 570 appUsages.remove(appAttr) 571 } 572 } 573 574 // Any remaining truested attributions must be for non-proxy usages, so add them 575 for ((packageAttr, label) in approvedAttrs) { 576 approvedLabels[packageAttr] = listOf(label) 577 } 578 579 removeDuplicates(appUsages, approvedLabels.keys) 580 581 postValue(Usages(appUsages, callOpUsages, approvedLabels)) 582 } 583 584 /** Merge any usages for the same app which don't have a special attribution */ 585 private fun removeDuplicates( 586 appUsages: MutableMap<PackageAttribution, Set<String>>, 587 approvedUsages: Collection<PackageAttribution> 588 ) { 589 // Iterate over all non-special attribution keys 590 for (packageAttr in appUsages.keys.minus(approvedUsages)) { 591 var groupSet = appUsages[packageAttr] ?: continue 592 593 for (otherAttr in appUsages.keys.minus(approvedUsages)) { 594 if (otherAttr.pkgEq(packageAttr)) { 595 groupSet = groupSet.plus(appUsages[otherAttr] ?: emptySet()) 596 appUsages.remove(otherAttr) 597 } 598 } 599 appUsages[packageAttr] = groupSet 600 } 601 } 602 } 603 604 private fun getPackageAttr(usage: OpAccess): PackageAttribution { 605 return PackageAttribution(usage.attributionTag, usage.packageName, usage.user) 606 } 607 } 608 609 /** 610 * Factory for a ReviewOngoingUsageViewModel 611 * 612 * @param extraDurationMillis The number of milliseconds old usages are considered for 613 * @param owner The owner of this saved state 614 * @param defaultArgs The default args to pass 615 */ 616 class ReviewOngoingUsageViewModelFactory( 617 private val extraDurationMillis: Long, 618 owner: SavedStateRegistryOwner, 619 defaultArgs: Bundle 620 ) : AbstractSavedStateViewModelFactory(owner, defaultArgs) { createnull621 override fun <T : ViewModel> create( 622 key: String, 623 modelClass: Class<T>, 624 handle: SavedStateHandle 625 ): T { 626 handle.set( 627 FIRST_OPENED_KEY, 628 handle.get<Long>(FIRST_OPENED_KEY) ?: System.currentTimeMillis() 629 ) 630 @Suppress("UNCHECKED_CAST") 631 return ReviewOngoingUsageViewModel(handle, extraDurationMillis) as T 632 } 633 } 634