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