1 /* 2 * Copyright (C) 2022 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.settingslib.spaprivileged.template.app 18 19 import android.app.AppOpsManager.MODE_ALLOWED 20 import android.app.AppOpsManager.MODE_DEFAULT 21 import android.app.AppOpsManager.MODE_ERRORED 22 import android.content.Context 23 import android.content.pm.ApplicationInfo 24 import androidx.compose.runtime.Composable 25 import androidx.compose.runtime.State 26 import androidx.compose.runtime.derivedStateOf 27 import androidx.compose.runtime.livedata.observeAsState 28 import androidx.compose.runtime.remember 29 import com.android.settingslib.spa.framework.compose.stateOf 30 import com.android.settingslib.spa.framework.util.asyncMapItem 31 import com.android.settingslib.spa.framework.util.filterItem 32 import com.android.settingslib.spaprivileged.model.app.AppOpsController 33 import com.android.settingslib.spaprivileged.model.app.AppRecord 34 import com.android.settingslib.spaprivileged.model.app.IAppOpsController 35 import com.android.settingslib.spaprivileged.model.app.IPackageManagers 36 import com.android.settingslib.spaprivileged.model.app.PackageManagers 37 import kotlinx.coroutines.flow.Flow 38 import kotlinx.coroutines.flow.combine 39 import kotlinx.coroutines.flow.map 40 41 data class AppOpPermissionRecord( 42 override val app: ApplicationInfo, 43 val hasRequestBroaderPermission: Boolean, 44 val hasRequestPermission: Boolean, 45 var appOpsController: IAppOpsController, 46 ) : AppRecord 47 48 abstract class AppOpPermissionListModel( 49 protected val context: Context, 50 private val packageManagers: IPackageManagers = PackageManagers, 51 ) : TogglePermissionAppListModel<AppOpPermissionRecord> { 52 53 abstract val appOp: Int 54 abstract val permission: String 55 56 /** 57 * When set, specifies the broader permission who trumps the [permission]. 58 * 59 * When trumped, the [permission] is not changeable and model shows the [permission] as allowed. 60 */ 61 open val broaderPermission: String? = null 62 63 /** 64 * Indicates whether [permission] has protection level appop flag. 65 * 66 * If true, it uses getAppOpPermissionPackages() to fetch bits to decide whether the permission 67 * is requested. 68 */ 69 open val permissionHasAppOpFlag: Boolean = true 70 71 open val modeForNotAllowed: Int = MODE_ERRORED 72 73 /** 74 * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed. 75 * 76 * Security or privacy related app-ops should be set with setUidMode() instead of setMode(). 77 */ 78 open val setModeByUid = false 79 80 /** These not changeable packages will also be hidden from app list. */ 81 private val notChangeablePackages = 82 setOf("android", "com.android.systemui", context.packageName) 83 84 private fun createAppOpsController(app: ApplicationInfo) = 85 AppOpsController( 86 context = context, 87 app = app, 88 op = appOp, 89 setModeByUid = setModeByUid, 90 modeForNotAllowed = modeForNotAllowed, 91 ) 92 93 private fun createRecord( 94 app: ApplicationInfo, 95 hasRequestPermission: Boolean 96 ): AppOpPermissionRecord = 97 with(packageManagers) { 98 AppOpPermissionRecord( 99 app = app, 100 hasRequestBroaderPermission = 101 broaderPermission?.let { app.hasRequestPermission(it) } ?: false, 102 hasRequestPermission = hasRequestPermission, 103 appOpsController = createAppOpsController(app), 104 ) 105 } 106 107 override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) = 108 if (permissionHasAppOpFlag) { 109 userIdFlow 110 .map { userId -> packageManagers.getAppOpPermissionPackages(userId, permission) } 111 .combine(appListFlow) { packageNames, appList -> 112 appList.map { app -> 113 createRecord( 114 app = app, 115 hasRequestPermission = app.packageName in packageNames, 116 ) 117 } 118 } 119 } else { 120 appListFlow.asyncMapItem { app -> 121 with(packageManagers) { createRecord(app, app.hasRequestPermission(permission)) } 122 } 123 } 124 125 override fun transformItem(app: ApplicationInfo) = 126 with(packageManagers) { 127 createRecord( 128 app = app, 129 hasRequestPermission = app.hasRequestPermission(permission), 130 ) 131 } 132 133 override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) = 134 recordListFlow.filterItem(::isChangeable) 135 136 /** 137 * Defining the default behavior as permissible as long as the package requested this permission 138 * (This means pre-M gets approval during install time; M apps gets approval during runtime). 139 */ 140 @Composable 141 override fun isAllowed(record: AppOpPermissionRecord): State<Boolean?> { 142 if (record.hasRequestBroaderPermission) { 143 // Broader permission trumps the specific permission. 144 return stateOf(true) 145 } 146 147 val mode = record.appOpsController.mode.observeAsState() 148 return remember { 149 derivedStateOf { 150 when (mode.value) { 151 null -> null 152 MODE_ALLOWED -> true 153 MODE_DEFAULT -> 154 with(packageManagers) { record.app.hasGrantPermission(permission) } 155 else -> false 156 } 157 } 158 } 159 } 160 161 override fun isChangeable(record: AppOpPermissionRecord) = 162 record.hasRequestPermission && 163 !record.hasRequestBroaderPermission && 164 record.app.packageName !in notChangeablePackages 165 166 override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { 167 record.appOpsController.setAllowed(newAllowed) 168 } 169 } 170