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.Application 21 import android.content.pm.PackageManager 22 import android.os.UserHandle 23 import android.util.Log 24 import com.android.modules.utils.build.SdkLevel 25 import com.android.permissioncontroller.appops.data.model.v31.PackageAppOpUsageModel 26 import com.android.permissioncontroller.appops.data.model.v31.PackageAppOpUsageModel.AppOpUsageModel 27 import com.android.permissioncontroller.permission.data.PackageBroadcastReceiver 28 import com.android.permissioncontroller.permission.data.repository.v31.PermissionRepository 29 import com.android.permissioncontroller.permission.utils.PermissionMapping 30 import kotlin.concurrent.Volatile 31 import kotlinx.coroutines.CoroutineDispatcher 32 import kotlinx.coroutines.Dispatchers 33 import kotlinx.coroutines.channels.awaitClose 34 import kotlinx.coroutines.flow.Flow 35 import kotlinx.coroutines.flow.callbackFlow 36 import kotlinx.coroutines.flow.flowOn 37 38 /** 39 * This repository encapsulate app op data (i.e. app op usage, app op mode, historical ops etc.) 40 * exposed by [AppOpsManager]. 41 */ 42 interface AppOpRepository { 43 /** 44 * A flow/stream of package app ops, these app ops are processed to show the usage statistics in 45 * the privacy dashboard. 46 * 47 * @see AppOpsManager.getPackagesForOps 48 */ 49 val packageAppOpsUsages: Flow<List<PackageAppOpUsageModel>> 50 51 companion object { 52 @Volatile private var instance: AppOpRepository? = null 53 54 fun getInstance( 55 application: Application, 56 permissionRepository: PermissionRepository 57 ): AppOpRepository = 58 instance 59 ?: synchronized(this) { 60 AppOpRepositoryImpl(application, permissionRepository).also { instance = it } 61 } 62 } 63 } 64 65 class AppOpRepositoryImpl( 66 application: Application, 67 private val permissionRepository: PermissionRepository, 68 private val dispatcher: CoroutineDispatcher = Dispatchers.Default, 69 ) : AppOpRepository { 70 private val appOpsManager = 71 checkNotNull(application.getSystemService(AppOpsManager::class.java)) 72 private val packageManager = application.packageManager 73 74 private val appOpNames = getPrivacyDashboardAppOpNames() 75 <lambda>null76 override val packageAppOpsUsages by lazy { 77 callbackFlow { 78 send(getPackageOps()) 79 80 // Suppress OnOpNotedListener lint error, startWatchingNoted is behind sdk check. 81 @SuppressWarnings("NewApi") 82 val callback = 83 object : 84 PackageManager.OnPermissionsChangedListener, 85 PackageBroadcastReceiver.PackageBroadcastListener, 86 AppOpsManager.OnOpActiveChangedListener, 87 AppOpsManager.OnOpNotedListener, 88 AppOpsManager.OnOpChangedListener { 89 override fun onPermissionsChanged(uid: Int) { 90 sendUpdate() 91 } 92 93 override fun onOpChanged(op: String?, packageName: String?) { 94 sendUpdate() 95 } 96 97 override fun onPackageUpdate(packageName: String) { 98 sendUpdate() 99 } 100 101 override fun onOpActiveChanged( 102 op: String, 103 uid: Int, 104 packageName: String, 105 active: Boolean 106 ) { 107 sendUpdate() 108 } 109 110 override fun onOpNoted( 111 op: String, 112 uid: Int, 113 packageName: String, 114 attributionTag: String?, 115 flags: Int, 116 result: Int 117 ) { 118 sendUpdate() 119 } 120 121 fun sendUpdate() { 122 trySend(getPackageOps()) 123 } 124 } 125 126 packageManager.addOnPermissionsChangeListener(callback) 127 PackageBroadcastReceiver.addAllCallback(callback) 128 appOpNames.forEach { opName -> 129 // TODO(b/262035952): We watch each active op individually as 130 // startWatchingActive only registers the callback if all ops are valid. 131 // Fix this behavior so if one op is invalid it doesn't affect the other ops. 132 try { 133 appOpsManager.startWatchingActive(arrayOf(opName), { it.run() }, callback) 134 } catch (ignored: IllegalArgumentException) { 135 // Older builds may not support all requested app ops. 136 } 137 138 try { 139 appOpsManager.startWatchingMode(opName, /* all packages */ null, callback) 140 } catch (ignored: IllegalArgumentException) { 141 // Older builds may not support all requested app ops. 142 } 143 144 if (SdkLevel.isAtLeastU()) { 145 try { 146 appOpsManager.startWatchingNoted(arrayOf(opName), callback) 147 } catch (ignored: IllegalArgumentException) { 148 // Older builds may not support all requested app ops. 149 } 150 } 151 } 152 153 awaitClose { 154 packageManager.removeOnPermissionsChangeListener(callback) 155 PackageBroadcastReceiver.removeAllCallback(callback) 156 appOpsManager.stopWatchingActive(callback) 157 appOpsManager.stopWatchingMode(callback) 158 if (SdkLevel.isAtLeastU()) { 159 appOpsManager.stopWatchingNoted(callback) 160 } 161 } 162 } 163 .flowOn(dispatcher) 164 } 165 getPackageOpsnull166 private fun getPackageOps(): List<PackageAppOpUsageModel> { 167 return try { 168 appOpsManager.getPackagesForOps(appOpNames.toTypedArray()) 169 } catch (e: NullPointerException) { 170 Log.w(LOG_TAG, "App ops not recognized, app ops list: $appOpNames") 171 // Older builds may not support all requested app ops. 172 emptyList() 173 } 174 .map { packageOps -> 175 PackageAppOpUsageModel( 176 packageOps.packageName, 177 packageOps.ops.map { opEntry -> 178 AppOpUsageModel( 179 opEntry.opStr, 180 opEntry.getLastAccessTime(OPS_LAST_ACCESS_FLAGS) 181 ) 182 }, 183 UserHandle.getUserHandleForUid(packageOps.uid).identifier 184 ) 185 } 186 } 187 getPrivacyDashboardAppOpNamesnull188 private fun getPrivacyDashboardAppOpNames(): Set<String> { 189 val permissionGroups = permissionRepository.getPermissionGroupsForPrivacyDashboard() 190 val opNames = mutableSetOf<String>() 191 for (permissionGroup in permissionGroups) { 192 val permissionNames = 193 PermissionMapping.getPlatformPermissionNamesOfGroup(permissionGroup) 194 for (permissionName in permissionNames) { 195 val opName = AppOpsManager.permissionToOp(permissionName) ?: continue 196 opNames.add(opName) 197 } 198 } 199 200 opNames.add(AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE) 201 opNames.add(AppOpsManager.OPSTR_PHONE_CALL_CAMERA) 202 if (SdkLevel.isAtLeastT()) { 203 opNames.add(AppOpsManager.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO) 204 } 205 return opNames 206 } 207 208 companion object { 209 private const val LOG_TAG = "AppOpUsageRepository" 210 211 private const val OPS_LAST_ACCESS_FLAGS = 212 AppOpsManager.OP_FLAG_SELF or 213 AppOpsManager.OP_FLAG_TRUSTED_PROXIED or 214 AppOpsManager.OP_FLAG_TRUSTED_PROXY 215 } 216 } 217