1 /*
<lambda>null2  * Copyright (C) 2023 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.permission.domain.usecase.v31
18 
19 import android.Manifest
20 import android.app.Application
21 import android.content.pm.ApplicationInfo
22 import android.content.pm.PackageInfo
23 import android.content.pm.PackageManager
24 import android.os.UserHandle
25 import android.util.Log
26 import com.android.modules.utils.build.SdkLevel
27 import com.android.permissioncontroller.appops.data.model.v31.PackageAppOpUsageModel
28 import com.android.permissioncontroller.appops.data.repository.v31.AppOpRepository
29 import com.android.permissioncontroller.permission.data.repository.v31.PermissionRepository
30 import com.android.permissioncontroller.permission.domain.model.v31.PackagePermissionGroupUsageModel
31 import com.android.permissioncontroller.permission.domain.model.v31.PermissionGroupUsageModel
32 import com.android.permissioncontroller.permission.utils.PermissionMapping
33 import com.android.permissioncontroller.pm.data.repository.v31.PackageRepository
34 import com.android.permissioncontroller.role.data.repository.v31.RoleRepository
35 import com.android.permissioncontroller.user.data.repository.v31.UserRepository
36 import kotlinx.coroutines.flow.Flow
37 import kotlinx.coroutines.flow.map
38 
39 /**
40  * This use case read app ops data and transform that data to show the private data access by apps
41  * in privacy dashboard.
42  */
43 class GetPermissionGroupUsageUseCase(
44     private val packageRepository: PackageRepository,
45     private val permissionRepository: PermissionRepository,
46     private val appOpRepository: AppOpRepository,
47     private val roleRepository: RoleRepository,
48     private val userRepository: UserRepository,
49 ) {
50     /**
51      * Returns a flow (i.e. a stream) of permission group usages (i.e. the private data accesses)
52      * for privacy dashboard page.
53      */
54     operator fun invoke(): Flow<List<PermissionGroupUsageModel>> {
55         return appOpRepository.packageAppOpsUsages.map { packagesOps ->
56             val exemptedPackages = roleRepository.getExemptedPackages()
57             val currentUsers = userRepository.getUserProfilesIncludingCurrentUser()
58 
59             packagesOps
60                 .mapToPermissionGroups()
61                 .filter { it.userId in currentUsers }
62                 .filter { it.packageName !in exemptedPackages }
63                 .filterQuietProfilesIfNeeded(currentUsers)
64                 .filterNonRequestedOps()
65                 .buildPermissionGroupUsageModels()
66         }
67     }
68 
69     /** filter private space usages if needed. */
70     private suspend fun List<PackagePermissionGroupUsageModel>.filterQuietProfilesIfNeeded(
71         currentUsers: List<Int>
72     ): List<PackagePermissionGroupUsageModel> {
73         if (!SdkLevel.isAtLeastV()) {
74             return this
75         }
76         val usersQuietModeEnabledMap =
77             currentUsers.associateWith { userId -> userRepository.isQuietModeEnabled(userId) }
78         val usersShouldShowInQuietModeMap =
79             currentUsers.associateWith { userId -> userRepository.shouldShowInQuietMode(userId) }
80         return filter {
81             val isQuietModeEnabled = checkNotNull(usersQuietModeEnabledMap[it.userId])
82             val shouldShowInQuietMode = checkNotNull(usersShouldShowInQuietModeMap[it.userId])
83             !isQuietModeEnabled || shouldShowInQuietMode
84         }
85     }
86 
87     private fun List<PackageAppOpUsageModel>.mapToPermissionGroups():
88         List<PackagePermissionGroupUsageModel> {
89         return mapNotNull { packageOps ->
90             val permissionGroupUsages =
91                 packageOps.usages
92                     .mapNotNull {
93                         val permissionGroup =
94                             PermissionMapping.getPlatformPermissionGroupForOp(it.appOpName)
95                         if (permissionGroup != null) {
96                             Pair(permissionGroup, it.lastAccessTimestampMillis)
97                         } else {
98                             Log.w(LOG_TAG, "No permission group found for op: ${it.appOpName}")
99                             null
100                         }
101                     }
102                     .groupBy { it.first } // group by permission group name
103                     .map { it -> // keep permission group and recent usage time
104                         it.key to it.value.map { it.second }.maxOf { it }
105                     }
106                     .toMap()
107 
108             if (permissionGroupUsages.isNotEmpty()) {
109                 PackagePermissionGroupUsageModel(
110                     packageOps.packageName,
111                     permissionGroupUsages,
112                     packageOps.userId
113                 )
114             } else {
115                 null
116             }
117         }
118     }
119 
120     /** Filter Ops where the corresponding permission group is no longer requested by the package */
121     private suspend fun List<PackagePermissionGroupUsageModel>.filterNonRequestedOps():
122         List<PackagePermissionGroupUsageModel> {
123         return mapNotNull { pkgOps ->
124             val userHandle = UserHandle.of(pkgOps.userId)
125             val packageInfo = packageRepository.getPackageInfo(pkgOps.packageName, userHandle)
126             val filteredOps =
127                 pkgOps.usages.filter { permissionGroupUsage ->
128                     packageInfo?.requestedPermissions?.any { permission ->
129                         permissionGroupUsage.key ==
130                             PermissionMapping.getGroupOfPlatformPermission(permission)
131                     }
132                         ?: false
133                 }
134             if (filteredOps.isNotEmpty()) {
135                 PackagePermissionGroupUsageModel(pkgOps.packageName, filteredOps, pkgOps.userId)
136             } else {
137                 null
138             }
139         }
140     }
141 
142     private suspend fun List<PackagePermissionGroupUsageModel>.buildPermissionGroupUsageModels():
143         List<PermissionGroupUsageModel> {
144         return flatMap { pkgOps ->
145             pkgOps.usages.map { permGroupLastAccessTimeEntry ->
146                 PermissionGroupUsageModel(
147                     permGroupLastAccessTimeEntry.key,
148                     permGroupLastAccessTimeEntry.value,
149                     isPermissionGroupUserSensitive(
150                         pkgOps.packageName,
151                         permGroupLastAccessTimeEntry.key,
152                         pkgOps.userId
153                     )
154                 )
155             }
156         }
157     }
158 
159     /**
160      * Determines if an app's permission group is user-sensitive. if the permission group is not
161      * user sensitive then its only shown when user choose `Show system` option
162      */
163     private suspend fun isPermissionGroupUserSensitive(
164         packageName: String,
165         permissionGroup: String,
166         userId: Int
167     ): Boolean {
168         if (isTelecomPackage(packageName, permissionGroup)) {
169             return false
170         }
171         val userHandle = UserHandle.of(userId)
172         val packageInfo = packageRepository.getPackageInfo(packageName, userHandle) ?: return false
173         // if not a system app, the permission group must be user sensitive
174         if (packageInfo.applicationFlags and ApplicationInfo.FLAG_SYSTEM == 0) {
175             return true
176         }
177 
178         packageInfo.requestedPermissions.forEachIndexed { index, permissionName ->
179             if (PermissionMapping.getGroupOfPlatformPermission(permissionName) == permissionGroup) {
180                 val permFlags =
181                     permissionRepository.getPermissionFlags(permissionName, packageName, userHandle)
182                 val packageFlags = packageInfo.requestedPermissionsFlags[index]
183                 val isPermissionGranted =
184                     packageFlags and PackageInfo.REQUESTED_PERMISSION_GRANTED != 0 &&
185                         permFlags and PackageManager.FLAG_PERMISSION_REVOKED_COMPAT == 0
186                 if (isPermissionUserSensitive(isPermissionGranted, permFlags)) {
187                     return true
188                 }
189             }
190         }
191         return false
192     }
193 
194     private fun isTelecomPackage(packageName: String, permissionGroup: String): Boolean {
195         return packageName == TELECOM_PACKAGE &&
196             (permissionGroup == Manifest.permission_group.CAMERA ||
197                 permissionGroup == Manifest.permission_group.MICROPHONE)
198     }
199 
200     private fun isPermissionUserSensitive(
201         isPermissionGranted: Boolean,
202         permissionFlags: Int
203     ): Boolean {
204         return if (isPermissionGranted) {
205             permissionFlags and PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED != 0
206         } else {
207             permissionFlags and PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED != 0
208         }
209     }
210 
211     companion object {
212         private val LOG_TAG = GetPermissionGroupUsageUseCase::class.java.simpleName
213         private const val TELECOM_PACKAGE = "com.android.server.telecom"
214 
215         fun create(app: Application): GetPermissionGroupUsageUseCase {
216             val permissionRepository = PermissionRepository.getInstance(app)
217             val userRepository = UserRepository.getInstance(app)
218             val packageRepository = PackageRepository.getInstance(app)
219             val roleRepository = RoleRepository.getInstance(app)
220             val appOpRepository = AppOpRepository.getInstance(app, permissionRepository)
221 
222             return GetPermissionGroupUsageUseCase(
223                 packageRepository,
224                 permissionRepository,
225                 appOpRepository,
226                 roleRepository,
227                 userRepository
228             )
229         }
230     }
231 }
232