• 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 @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