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