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