• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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