• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2019 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.data
18 
19 import android.Manifest.permission_group.READ_MEDIA_VISUAL
20 import android.Manifest.permission_group.STORAGE
21 import android.app.AppOpsManager
22 import android.app.AppOpsManager.MODE_ALLOWED
23 import android.app.AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES
24 import android.app.Application
25 import android.app.role.RoleManager
26 import android.content.pm.PackageManager
27 import android.content.pm.PermissionInfo
28 import android.os.Build
29 import android.os.UserHandle
30 import android.permission.PermissionManager
31 import android.util.Log
32 import com.android.permissioncontroller.PermissionControllerApplication
33 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
34 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
35 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission
36 import com.android.permissioncontroller.permission.utils.KotlinUtils
37 import com.android.permissioncontroller.permission.utils.LocationUtils
38 import com.android.permissioncontroller.permission.utils.Utils
39 import com.android.permissioncontroller.permission.utils.Utils.OS_PKG
40 
41 /**
42  * A LiveData which represents the permissions for one package and permission group.
43  *
44  * @param app The current application
45  * @param packageName The name of the package
46  * @param permGroupName The name of the permission group
47  * @param user The user of the package
48  */
49 class LightAppPermGroupLiveData
50 private constructor(
51     private val app: Application,
52     private val packageName: String,
53     private val permGroupName: String,
54     private val user: UserHandle,
55     private val deviceId: Int
56 ) : SmartUpdateMediatorLiveData<LightAppPermGroup?>(), LocationUtils.LocationListener {
57 
58     private val LOG_TAG = this::class.java.simpleName
59 
60     private var isSpecialLocation = false
61     private val permStateLiveData = PermStateLiveData[packageName, permGroupName, user, deviceId]
62     private val permGroupLiveData = PermGroupLiveData[permGroupName]
63     private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user, deviceId]
64     private val fgPermNamesLiveData = ForegroundPermNamesLiveData
65 
66     init {
67         isSpecialLocation =
68             LocationUtils.isLocationGroupAndProvider(app, permGroupName, packageName) ||
69                 LocationUtils.isLocationGroupAndControllerExtraPackage(
70                     app,
71                     permGroupName,
72                     packageName
73                 )
74 
75         addSource(fgPermNamesLiveData) { update() }
76 
77         val key = KotlinUtils.Quadruple(packageName, permGroupName, user, deviceId)
78 
79         addSource(permStateLiveData) { permStates ->
80             if (permStates == null && permStateLiveData.isInitialized) {
81                 invalidateSingle(key)
82                 value = null
83             } else {
84                 update()
85             }
86         }
87 
88         addSource(permGroupLiveData) { permGroup ->
89             if (permGroup == null && permGroupLiveData.isInitialized) {
90                 invalidateSingle(key)
91                 value = null
92             } else {
93                 update()
94             }
95         }
96 
97         addSource(packageInfoLiveData) { packageInfo ->
98             if (packageInfo == null && packageInfoLiveData.isInitialized) {
99                 invalidateSingle(key)
100                 value = null
101             } else {
102                 update()
103             }
104         }
105     }
106 
107     override fun onUpdate() {
108         val permStates = permStateLiveData.value ?: return
109         val permGroup = permGroupLiveData.value ?: return
110         val packageInfo = packageInfoLiveData.value ?: return
111         val allForegroundPerms = fgPermNamesLiveData.value ?: return
112 
113         // Do not allow toggling pre-M custom perm groups
114         if (
115             packageInfo.targetSdkVersion < Build.VERSION_CODES.M &&
116                 permGroup.groupInfo.packageName != OS_PKG
117         ) {
118             value = LightAppPermGroup(packageInfo, permGroup.groupInfo, emptyMap())
119             return
120         }
121 
122         val permissionMap = mutableMapOf<String, LightPermission>()
123         for ((permName, permState) in permStates) {
124             val permInfo = permGroup.permissionInfos[permName] ?: continue
125             val foregroundPerms = allForegroundPerms[permName]
126             permissionMap[permName] =
127                 LightPermission(packageInfo, permInfo, permState, foregroundPerms)
128         }
129 
130 
131         val hasInstallToRuntimeSplit = hasInstallToRuntimeSplit(packageInfo, permissionMap)
132         value =
133             LightAppPermGroup(
134                 packageInfo,
135                 permGroup.groupInfo,
136                 permissionMap,
137                 hasInstallToRuntimeSplit,
138                 isSpecialLocationGranted(app, packageName, permGroupName, user),
139                 isSpecialFixedStorageGranted(app, packageName, permGroupName, packageInfo.uid)
140             )
141     }
142 
143     /**
144      * Check if permission group contains a runtime permission that split from an installed
145      * permission and the split happened in an Android version higher than app's targetSdk.
146      *
147      * @return `true` if there is such permission, `false` otherwise
148      */
149     private fun hasInstallToRuntimeSplit(
150         packageInfo: LightPackageInfo,
151         permissionMap: Map<String, LightPermission>
152     ): Boolean {
153         val permissionManager = app.getSystemService(PermissionManager::class.java) ?: return false
154 
155         for (spi in permissionManager.splitPermissions) {
156             val splitPerm = spi.splitPermission
157 
158             val pi =
159                 try {
160                     app.packageManager.getPermissionInfo(splitPerm, 0)
161                 } catch (e: PackageManager.NameNotFoundException) {
162                     Log.w(LOG_TAG, "No such permission: $splitPerm", e)
163                     continue
164                 }
165 
166             // Skip if split permission is not "install" permission.
167             if (pi.protection != PermissionInfo.PROTECTION_NORMAL) {
168                 continue
169             }
170 
171             val newPerms = spi.newPermissions
172             for (permName in newPerms) {
173                 val newPerm = permissionMap[permName]?.permInfo ?: continue
174 
175                 // Skip if new permission is not "runtime" permission.
176                 if (newPerm.protection != PermissionInfo.PROTECTION_DANGEROUS) {
177                     continue
178                 }
179 
180                 if (packageInfo.targetSdkVersion < spi.targetSdk) {
181                     return true
182                 }
183             }
184         }
185         return false
186     }
187 
188     override fun onLocationStateChange(enabled: Boolean) {
189         update()
190     }
191 
192     override fun onActive() {
193         super.onActive()
194 
195         if (isSpecialLocation) {
196             LocationUtils.addLocationListener(this)
197             update()
198         }
199     }
200 
201     override fun onInactive() {
202         if (isSpecialLocation) {
203             LocationUtils.removeLocationListener(this)
204         }
205 
206         super.onInactive()
207     }
208 
209     /**
210      * Repository for AppPermGroupLiveDatas.
211      *
212      * <p> Key value is a triple of string package name, string permission group name, and
213      * UserHandle, value is its corresponding LiveData.
214      */
215     companion object :
216         DataRepositoryForDevice<
217             KotlinUtils.Quadruple<String, String, UserHandle, Int>, LightAppPermGroupLiveData
218         >() {
219         override fun newValue(
220             key: KotlinUtils.Quadruple<String, String, UserHandle, Int>,
221             deviceId: Int
222         ): LightAppPermGroupLiveData {
223             return LightAppPermGroupLiveData(
224                 PermissionControllerApplication.get(),
225                 key.first,
226                 key.second,
227                 key.third,
228                 deviceId
229             )
230         }
231 
232         private const val SYSTEM_GALLERY_ROLE_NAME = "android.app.role.SYSTEM_GALLERY"
233 
234         // Returns true if this app is the location provider or location extra package, and location
235         // access is granted, false if it is the provider/extra, and location is not granted, and
236         // null if it is not a special package
237         fun isSpecialLocationGranted(
238             app: Application,
239             packageName: String,
240             permGroupName: String,
241             user: UserHandle
242         ): Boolean? {
243             val userContext = Utils.getUserContext(app, user)
244             return if (
245                 LocationUtils.isLocationGroupAndProvider(userContext, permGroupName, packageName)
246             ) {
247                 LocationUtils.isLocationEnabled(userContext)
248             } else if (
249                 LocationUtils.isLocationGroupAndControllerExtraPackage(app, permGroupName, packageName)
250             ) {
251                 // The permission of the extra location controller package is determined by the status
252                 // of the controller package itself.
253                 LocationUtils.isExtraLocationControllerPackageEnabled(userContext)
254             } else {
255                 null
256             }
257         }
258 
259         // Gallery role is static, so we only need to get the set gallery app once
260         private val systemGalleryApps: List<String> by lazy {
261             val roleManager = PermissionControllerApplication.get()
262                 .getSystemService(RoleManager::class.java) ?: return@lazy emptyList()
263             roleManager.getRoleHolders(SYSTEM_GALLERY_ROLE_NAME)
264         }
265 
266         fun isSpecialFixedStorageGranted(
267             app: Application,
268             packageName: String,
269             permGroupName: String,
270             uid: Int
271         ): Boolean {
272             if (permGroupName != READ_MEDIA_VISUAL && permGroupName != STORAGE) {
273                 return false
274             }
275             if (packageName !in systemGalleryApps) {
276                 return false
277             }
278             // This is the storage group, and the gallery app. Check the write media app op
279             val appOps = app.getSystemService(AppOpsManager::class.java)
280             return appOps.checkOpNoThrow(OPSTR_WRITE_MEDIA_IMAGES, uid, packageName) == MODE_ALLOWED
281         }
282     }
283 }
284