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