• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.appops.data.repository.v31
18 
19 import android.app.AppOpsManager
20 import android.app.AppOpsManager.AttributedOpEntry
21 import android.app.AppOpsManager.HISTORY_FLAG_DISCRETE
22 import android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS
23 import android.app.AppOpsManager.HistoricalOps
24 import android.app.AppOpsManager.HistoricalOpsRequest
25 import android.app.AppOpsManager.OP_FLAG_SELF
26 import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED
27 import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY
28 import android.app.Application
29 import android.os.UserHandle
30 import android.util.Log
31 import com.android.modules.utils.build.SdkLevel
32 import com.android.permissioncontroller.DeviceUtils
33 import com.android.permissioncontroller.appops.data.model.v31.DiscretePackageOpsModel
34 import com.android.permissioncontroller.appops.data.model.v31.DiscretePackageOpsModel.DiscreteOpModel
35 import com.android.permissioncontroller.appops.data.model.v31.PackageAppOpUsageModel
36 import com.android.permissioncontroller.appops.data.model.v31.PackageAppOpUsageModel.AppOpUsageModel
37 import com.android.permissioncontroller.data.repository.v31.AppOpChangeListener
38 import com.android.permissioncontroller.data.repository.v31.PackageChangeListener
39 import com.android.permissioncontroller.data.repository.v31.PermissionChangeListener
40 import com.android.permissioncontroller.permission.data.repository.v31.PermissionRepository
41 import com.android.permissioncontroller.permission.utils.PermissionMapping
42 import java.util.concurrent.TimeUnit
43 import kotlin.concurrent.Volatile
44 import kotlin.coroutines.suspendCoroutine
45 import kotlinx.coroutines.CoroutineDispatcher
46 import kotlinx.coroutines.CoroutineScope
47 import kotlinx.coroutines.Dispatchers
48 import kotlinx.coroutines.Job
49 import kotlinx.coroutines.channels.awaitClose
50 import kotlinx.coroutines.flow.Flow
51 import kotlinx.coroutines.flow.callbackFlow
52 import kotlinx.coroutines.flow.flowOn
53 import kotlinx.coroutines.launch
54 
55 /**
56  * This repository encapsulate app op data (i.e. app op usage, app op mode, historical ops etc.)
57  * exposed by [AppOpsManager].
58  */
59 interface AppOpRepository {
60     /**
61      * A flow/stream of package app ops, these app ops are processed to show the usage statistics in
62      * the privacy dashboard.
63      *
64      * @see AppOpsManager.getPackagesForOps
65      */
66     val packageAppOpsUsages: Flow<List<PackageAppOpUsageModel>>
67 
68     /**
69      * Returns a flow of discrete package ops.
70      *
71      * @param opNames A list of app op names.
72      * @param coroutineScope the coroutine scope where we fetch the data asynchronously.
73      */
74     fun getDiscreteOps(
75         opNames: List<String>,
76         coroutineScope: CoroutineScope,
77     ): Flow<List<DiscretePackageOpsModel>>
78 
79     companion object {
80         @Volatile private var instance: AppOpRepository? = null
81 
82         fun getInstance(
83             application: Application,
84             permissionRepository: PermissionRepository,
85         ): AppOpRepository =
86             instance
87                 ?: synchronized(this) {
88                     AppOpRepositoryImpl(application, permissionRepository).also { instance = it }
89                 }
90     }
91 }
92 
93 class AppOpRepositoryImpl(
94     application: Application,
95     private val permissionRepository: PermissionRepository,
96     private val dispatcher: CoroutineDispatcher = Dispatchers.Default,
97 ) : AppOpRepository {
98     private val appOpsManager =
99         checkNotNull(application.getSystemService(AppOpsManager::class.java))
100     private val packageManager = application.packageManager
101 
102     private val appOpNames = getPrivacyDashboardAppOpNames()
103 
getDiscreteOpsnull104     override fun getDiscreteOps(
105         opNames: List<String>,
106         coroutineScope: CoroutineScope,
107     ): Flow<List<DiscretePackageOpsModel>> {
108         return callbackFlow {
109                 var job: Job? = null
110                 send(getDiscreteOps(opNames))
111 
112                 fun sendUpdate() {
113                     if (job == null || job?.isActive == false) {
114                         job = coroutineScope.launch(dispatcher) { trySend(getDiscreteOps(opNames)) }
115                     }
116                 }
117 
118                 val appOpListener =
119                     AppOpChangeListener(opNames.toSet(), appOpsManager) { sendUpdate() }
120                 val packageListener = PackageChangeListener { sendUpdate() }
121                 val permissionListener = PermissionChangeListener(packageManager) { sendUpdate() }
122                 packageListener.register()
123                 appOpListener.register()
124                 permissionListener.register()
125                 awaitClose {
126                     appOpListener.unregister()
127                     packageListener.unregister()
128                     permissionListener.unregister()
129                 }
130             }
131             .flowOn(dispatcher)
132     }
133 
getDiscreteOpsnull134     private suspend fun getDiscreteOps(opNames: List<String>): List<DiscretePackageOpsModel> {
135         val duration =
136             if (DeviceUtils.isHandheld()) TimeUnit.DAYS.toMillis(7) else TimeUnit.DAYS.toMillis(1)
137         val currentTime = System.currentTimeMillis()
138         val beginTimeMillis = currentTime - duration
139         val request =
140             HistoricalOpsRequest.Builder(beginTimeMillis, currentTime)
141                 .setFlags(OP_FLAG_SELF or OP_FLAG_TRUSTED_PROXIED)
142                 .setOpNames(opNames)
143                 .setHistoryFlags(HISTORY_FLAG_DISCRETE or HISTORY_FLAG_GET_ATTRIBUTION_CHAINS)
144                 .build()
145         val historicalOps: HistoricalOps = suspendCoroutine {
146             appOpsManager.getHistoricalOps(request, { it.run() }) { ops: HistoricalOps ->
147                 it.resumeWith(Result.success(ops))
148             }
149         }
150         val discreteOpsResult = mutableListOf<DiscretePackageOpsModel>()
151         // Read through nested (uid -> package name -> attribution tag -> op -> discrete events)
152         // historical ops data structure
153         for (uidIndex in 0 until historicalOps.uidCount) {
154             val historicalUidOps = historicalOps.getUidOpsAt(uidIndex)
155             val userId = UserHandle.getUserHandleForUid(historicalUidOps.uid).identifier
156             for (packageIndex in 0 until historicalUidOps.packageCount) {
157                 val historicalPackageOps = historicalUidOps.getPackageOpsAt(packageIndex)
158                 val packageName = historicalPackageOps.packageName
159                 val appOpEvents = mutableListOf<DiscreteOpModel>()
160                 for (tagIndex in 0 until historicalPackageOps.attributedOpsCount) {
161                     val attributedHistoricalOps = historicalPackageOps.getAttributedOpsAt(tagIndex)
162                     for (opIndex in 0 until attributedHistoricalOps.opCount) {
163                         val historicalOp = attributedHistoricalOps.getOpAt(opIndex)
164                         for (index in 0 until historicalOp.discreteAccessCount) {
165                             val attributedOpEntry: AttributedOpEntry =
166                                 historicalOp.getDiscreteAccessAt(index)
167                             val proxy = attributedOpEntry.getLastProxyInfo(OPS_LAST_ACCESS_FLAGS)
168                             val opEvent =
169                                 DiscreteOpModel(
170                                     opName = historicalOp.opName,
171                                     accessTimeMillis =
172                                         attributedOpEntry.getLastAccessTime(OPS_LAST_ACCESS_FLAGS),
173                                     durationMillis =
174                                         attributedOpEntry.getLastDuration(OPS_LAST_ACCESS_FLAGS),
175                                     attributionTag = attributedHistoricalOps.tag,
176                                     proxyPackageName = proxy?.packageName,
177                                     proxyUserId =
178                                         proxy?.uid?.let {
179                                             UserHandle.getUserHandleForUid(it).identifier
180                                         },
181                                 )
182                             appOpEvents.add(opEvent)
183                         }
184                     }
185                 }
186                 discreteOpsResult.add(DiscretePackageOpsModel(packageName, userId, appOpEvents))
187             }
188         }
189         return discreteOpsResult
190     }
191 
<lambda>null192     override val packageAppOpsUsages by lazy {
193         callbackFlow {
194                 send(getPackageOps())
195 
196                 fun sendUpdate() {
197                     trySend(getPackageOps())
198                 }
199 
200                 val appOpListener = AppOpChangeListener(appOpNames, appOpsManager) { sendUpdate() }
201                 val packageListener = PackageChangeListener { sendUpdate() }
202                 val permissionListener = PermissionChangeListener(packageManager) { sendUpdate() }
203                 packageListener.register()
204                 appOpListener.register()
205                 permissionListener.register()
206                 awaitClose {
207                     appOpListener.unregister()
208                     packageListener.unregister()
209                     permissionListener.unregister()
210                 }
211             }
212             .flowOn(dispatcher)
213     }
214 
getPackageOpsnull215     private fun getPackageOps(): List<PackageAppOpUsageModel> {
216         return try {
217                 appOpsManager.getPackagesForOps(appOpNames.toTypedArray())
218             } catch (e: NullPointerException) {
219                 Log.w(LOG_TAG, "App ops not recognized, app ops list: $appOpNames")
220                 // Older builds may not support all requested app ops.
221                 emptyList()
222             }
223             .map { packageOps ->
224                 PackageAppOpUsageModel(
225                     packageOps.packageName,
226                     packageOps.ops.map { opEntry ->
227                         AppOpUsageModel(
228                             opEntry.opStr,
229                             opEntry.getLastAccessTime(OPS_LAST_ACCESS_FLAGS),
230                         )
231                     },
232                     UserHandle.getUserHandleForUid(packageOps.uid).identifier,
233                 )
234             }
235     }
236 
getPrivacyDashboardAppOpNamesnull237     private fun getPrivacyDashboardAppOpNames(): Set<String> {
238         val permissionGroups = permissionRepository.getPermissionGroupsForPrivacyDashboard()
239         val opNames = mutableSetOf<String>()
240         for (permissionGroup in permissionGroups) {
241             val permissionNames =
242                 PermissionMapping.getPlatformPermissionNamesOfGroup(permissionGroup)
243             for (permissionName in permissionNames) {
244                 val opName = AppOpsManager.permissionToOp(permissionName) ?: continue
245                 opNames.add(opName)
246             }
247         }
248 
249         opNames.add(AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE)
250         opNames.add(AppOpsManager.OPSTR_PHONE_CALL_CAMERA)
251         if (SdkLevel.isAtLeastT()) {
252             opNames.add(AppOpsManager.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO)
253         }
254         if (SdkLevel.isAtLeastV()) {
255             opNames.add(AppOpsManager.OPSTR_EMERGENCY_LOCATION)
256         }
257         return opNames
258     }
259 
260     companion object {
261         private const val LOG_TAG = "AppOpUsageRepository"
262 
263         private const val OPS_LAST_ACCESS_FLAGS =
264             OP_FLAG_SELF or OP_FLAG_TRUSTED_PROXIED or OP_FLAG_TRUSTED_PROXY
265     }
266 }
267