Merge "Add AppOpPermissionAppList for SpaPrivilegedLib"
This commit is contained in:
@@ -27,6 +27,7 @@ android_library {
|
||||
"androidx.compose.material3_material3",
|
||||
"androidx.compose.material_material-icons-extended",
|
||||
"androidx.compose.runtime_runtime",
|
||||
"androidx.compose.runtime_runtime-livedata",
|
||||
"androidx.compose.ui_ui-tooling-preview",
|
||||
"androidx.navigation_navigation-compose",
|
||||
"com.google.android.material_material",
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
|
||||
class AppOpsController(
|
||||
context: Context,
|
||||
@@ -32,21 +33,23 @@ class AppOpsController(
|
||||
) {
|
||||
private val appOpsManager = checkNotNull(context.getSystemService(AppOpsManager::class.java))
|
||||
|
||||
val mode: LiveData<Int>
|
||||
get() = _mode
|
||||
val isAllowed: LiveData<Boolean>
|
||||
get() = _isAllowed
|
||||
get() = Transformations.map(_mode) { it == MODE_ALLOWED }
|
||||
|
||||
fun setAllowed(allowed: Boolean) {
|
||||
val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
|
||||
appOpsManager.setMode(op, app.uid, app.packageName, mode)
|
||||
_isAllowed.postValue(allowed)
|
||||
_mode.postValue(mode)
|
||||
}
|
||||
|
||||
@Mode
|
||||
fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
|
||||
|
||||
private val _isAllowed = object : MutableLiveData<Boolean>() {
|
||||
private val _mode = object : MutableLiveData<Int>() {
|
||||
override fun onActive() {
|
||||
postValue(getMode() == MODE_ALLOWED)
|
||||
postValue(getMode())
|
||||
}
|
||||
|
||||
override fun onInactive() {
|
||||
|
||||
@@ -16,31 +16,52 @@
|
||||
|
||||
package com.android.settingslib.spaprivileged.model.app
|
||||
|
||||
import android.app.AppGlobals
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import com.android.settingslib.spa.framework.util.asyncFilter
|
||||
|
||||
private const val TAG = "PackageManagers"
|
||||
|
||||
object PackageManagers {
|
||||
fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo =
|
||||
PackageManager.getPackageInfoAsUserCached(packageName, 0, userId)
|
||||
private val iPackageManager by lazy { AppGlobals.getPackageManager() }
|
||||
|
||||
fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
|
||||
getPackageInfoAsUser(packageName, 0, userId)
|
||||
|
||||
fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo =
|
||||
PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
|
||||
|
||||
fun hasRequestPermission(app: ApplicationInfo, permission: String): Boolean {
|
||||
val packageInfo = try {
|
||||
PackageManager.getPackageInfoAsUserCached(
|
||||
app.packageName, PackageManager.GET_PERMISSIONS.toLong(), app.userId
|
||||
)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.w(TAG, "getPackageInfoAsUserCached() failed", e)
|
||||
return false
|
||||
}
|
||||
fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
|
||||
val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
|
||||
return packageInfo?.requestedPermissions?.let {
|
||||
permission in it
|
||||
} ?: false
|
||||
}
|
||||
|
||||
fun ApplicationInfo.hasGrantPermission(permission: String): Boolean {
|
||||
val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
|
||||
?: return false
|
||||
val index = packageInfo.requestedPermissions.indexOf(permission)
|
||||
return index >= 0 &&
|
||||
packageInfo.requestedPermissionsFlags[index].hasFlag(REQUESTED_PERMISSION_GRANTED)
|
||||
}
|
||||
|
||||
suspend fun getAppOpPermissionPackages(userId: Int, permission: String): Set<String> =
|
||||
iPackageManager.getAppOpPermissionPackages(permission, userId).asIterable().asyncFilter {
|
||||
iPackageManager.isPackageAvailable(it, userId)
|
||||
}.toSet()
|
||||
|
||||
private fun getPackageInfoAsUser(packageName: String, flags: Int, userId: Int): PackageInfo? =
|
||||
try {
|
||||
PackageManager.getPackageInfoAsUserCached(packageName, flags.toLong(), userId)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.w(TAG, "getPackageInfoAsUserCached() failed", e)
|
||||
null
|
||||
}
|
||||
|
||||
private fun Int.hasFlag(flag: Int) = (this and flag) > 0
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@ fun AppInfo(packageName: String, userId: Int) {
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
val packageInfo = remember { PackageManagers.getPackageInfoAsUser(packageName, userId) }
|
||||
val packageInfo =
|
||||
remember { PackageManagers.getPackageInfoAsUser(packageName, userId) } ?: return
|
||||
Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
|
||||
AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.spaprivileged.template.app
|
||||
|
||||
import android.app.AppOpsManager.MODE_ALLOWED
|
||||
import android.app.AppOpsManager.MODE_DEFAULT
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import com.android.settingslib.spaprivileged.model.app.AppOpsController
|
||||
import com.android.settingslib.spaprivileged.model.app.AppRecord
|
||||
import com.android.settingslib.spaprivileged.model.app.PackageManagers
|
||||
import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasGrantPermission
|
||||
import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasRequestPermission
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
data class AppOpPermissionRecord(
|
||||
override val app: ApplicationInfo,
|
||||
val hasRequestPermission: Boolean,
|
||||
var appOpsController: AppOpsController,
|
||||
) : AppRecord
|
||||
|
||||
abstract class AppOpPermissionListModel(private val context: Context) :
|
||||
TogglePermissionAppListModel<AppOpPermissionRecord> {
|
||||
|
||||
abstract val appOp: Int
|
||||
abstract val permission: String
|
||||
|
||||
private val notChangeablePackages =
|
||||
setOf("android", "com.android.systemui", context.packageName)
|
||||
|
||||
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
|
||||
userIdFlow.map { userId ->
|
||||
PackageManagers.getAppOpPermissionPackages(userId, permission)
|
||||
}.combine(appListFlow) { packageNames, appList ->
|
||||
appList.map { app ->
|
||||
AppOpPermissionRecord(
|
||||
app = app,
|
||||
hasRequestPermission = app.packageName in packageNames,
|
||||
appOpsController = AppOpsController(context = context, app = app, op = appOp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord(
|
||||
app = app,
|
||||
hasRequestPermission = app.hasRequestPermission(permission),
|
||||
appOpsController = AppOpsController(context = context, app = app, op = appOp),
|
||||
)
|
||||
|
||||
override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
|
||||
recordListFlow.map { recordList ->
|
||||
recordList.filter { it.hasRequestPermission }
|
||||
}
|
||||
|
||||
/**
|
||||
* Defining the default behavior as permissible as long as the package requested this permission
|
||||
* (This means pre-M gets approval during install time; M apps gets approval during runtime).
|
||||
*/
|
||||
@Composable
|
||||
override fun isAllowed(record: AppOpPermissionRecord): State<Boolean?> {
|
||||
val mode = record.appOpsController.mode.observeAsState()
|
||||
return remember {
|
||||
derivedStateOf {
|
||||
when (mode.value) {
|
||||
null -> null
|
||||
MODE_ALLOWED -> true
|
||||
MODE_DEFAULT -> record.app.hasGrantPermission(permission)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isChangeable(record: AppOpPermissionRecord) =
|
||||
record.hasRequestPermission && record.app.packageName !in notChangeablePackages
|
||||
|
||||
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
|
||||
record.appOpsController.setAllowed(newAllowed)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user