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