Merge "Add AppOpPermissionAppList for SpaPrivilegedLib"

This commit is contained in:
Chaohui Wang
2022-09-13 12:00:55 +00:00
committed by Android (Google) Code Review
5 changed files with 143 additions and 16 deletions

View File

@@ -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",

View File

@@ -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() {

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}
}