mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 11:05:59 +00:00
feat: enhanced app permission management (#229)
This commit is contained in:
@@ -62,10 +62,11 @@ android {
|
|||||||
dependenciesInfo {
|
dependenciesInfo {
|
||||||
includeInApk = false
|
includeInApk = false
|
||||||
}
|
}
|
||||||
composeCompiler {
|
}
|
||||||
|
|
||||||
|
composeCompiler {
|
||||||
includeSourceInformation = false
|
includeSourceInformation = false
|
||||||
includeTraceMarkers = false
|
includeTraceMarkers = false
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
|||||||
@@ -7,12 +7,9 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.pm.ServiceInfo
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.ServiceCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -50,10 +47,7 @@ class LockTaskService: Service() {
|
|||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||||
.build()
|
.build()
|
||||||
ServiceCompat.startForeground(
|
startForeground(NotificationType.LockTaskMode.id, notification)
|
||||||
this, NotificationType.LockTaskMode.id, notification,
|
|
||||||
if (Build.VERSION.SDK_INT < 34) 0 else ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
|
|
||||||
)
|
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
val am = getSystemService(ActivityManager::class.java)
|
val am = getSystemService(ActivityManager::class.java)
|
||||||
delay(3000)
|
delay(3000)
|
||||||
|
|||||||
@@ -165,8 +165,12 @@ import com.bintianqi.owndroid.dpm.PasswordInfoScreen
|
|||||||
import com.bintianqi.owndroid.dpm.PasswordScreen
|
import com.bintianqi.owndroid.dpm.PasswordScreen
|
||||||
import com.bintianqi.owndroid.dpm.PermissionPolicy
|
import com.bintianqi.owndroid.dpm.PermissionPolicy
|
||||||
import com.bintianqi.owndroid.dpm.PermissionPolicyScreen
|
import com.bintianqi.owndroid.dpm.PermissionPolicyScreen
|
||||||
import com.bintianqi.owndroid.dpm.PermissionsManager
|
import com.bintianqi.owndroid.dpm.AppPermissionsManager
|
||||||
import com.bintianqi.owndroid.dpm.PermissionsManagerScreen
|
import com.bintianqi.owndroid.dpm.AppPermissionsManagerScreen
|
||||||
|
import com.bintianqi.owndroid.dpm.PermissionDetail
|
||||||
|
import com.bintianqi.owndroid.dpm.PermissionDetailScreen
|
||||||
|
import com.bintianqi.owndroid.dpm.PermissionManager
|
||||||
|
import com.bintianqi.owndroid.dpm.PermissionManagerScreen
|
||||||
import com.bintianqi.owndroid.dpm.PermittedAccessibilityServices
|
import com.bintianqi.owndroid.dpm.PermittedAccessibilityServices
|
||||||
import com.bintianqi.owndroid.dpm.PermittedAsAndImPackages
|
import com.bintianqi.owndroid.dpm.PermittedAsAndImPackages
|
||||||
import com.bintianqi.owndroid.dpm.PermittedInputMethods
|
import com.bintianqi.owndroid.dpm.PermittedInputMethods
|
||||||
@@ -566,10 +570,17 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
|||||||
::navigateToAppGroups, vm.appGroups, R.string.info_disable_user_control
|
::navigateToAppGroups, vm.appGroups, R.string.info_disable_user_control
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<PermissionsManager> {
|
composable<AppPermissionsManager> {
|
||||||
PermissionsManagerScreen(
|
AppPermissionsManagerScreen(
|
||||||
vm.packagePermissions, vm::getPackagePermissions, vm::setPackagePermission,
|
vm::getPackagePermissions, vm::setPackagePermission, ::navigateUp, it.toRoute()
|
||||||
::navigateUp, it.toRoute(), vm.chosenPackage, ::chooseSinglePackage
|
)
|
||||||
|
}
|
||||||
|
composable<PermissionManager> {
|
||||||
|
PermissionManagerScreen(::navigate, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<PermissionDetail> {
|
||||||
|
PermissionDetailScreen(
|
||||||
|
it.toRoute(), vm::getPermissionPackages, vm::setPackagePermission, ::navigateUp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<DisableMeteredData> {
|
composable<DisableMeteredData> {
|
||||||
|
|||||||
@@ -272,6 +272,6 @@ class MyRepository(val dbHelper: MyDbHelper) {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
fun deleteAllCrossProfileIntentFilters() {
|
fun deleteAllCrossProfileIntentFilters() {
|
||||||
dbHelper.writableDatabase.delete("cross_profile_intent_filters", null, null);
|
dbHelper.writableDatabase.delete("cross_profile_intent_filters", null, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,6 @@ import com.bintianqi.owndroid.dpm.doUserOperationWithContext
|
|||||||
import com.bintianqi.owndroid.dpm.getPackageInstaller
|
import com.bintianqi.owndroid.dpm.getPackageInstaller
|
||||||
import com.bintianqi.owndroid.dpm.globalSettings
|
import com.bintianqi.owndroid.dpm.globalSettings
|
||||||
import com.bintianqi.owndroid.dpm.handlePrivilegeChange
|
import com.bintianqi.owndroid.dpm.handlePrivilegeChange
|
||||||
import com.bintianqi.owndroid.dpm.isValidPackageName
|
|
||||||
import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage
|
import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage
|
||||||
import com.bintianqi.owndroid.dpm.runtimePermissions
|
import com.bintianqi.owndroid.dpm.runtimePermissions
|
||||||
import com.bintianqi.owndroid.dpm.secureSettings
|
import com.bintianqi.owndroid.dpm.secureSettings
|
||||||
@@ -280,20 +279,23 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
|||||||
getUcdPackages()
|
getUcdPackages()
|
||||||
}
|
}
|
||||||
|
|
||||||
val packagePermissions = MutableStateFlow(emptyMap<String, Int>())
|
fun getPackagePermissions(name: String): Map<String, Int> {
|
||||||
fun getPackagePermissions(name: String) {
|
return runtimePermissions.associate {
|
||||||
if (name.isValidPackageName) {
|
|
||||||
packagePermissions.value = runtimePermissions.associate {
|
|
||||||
it.id to DPM.getPermissionGrantState(DAR, name, it.id)
|
it.id to DPM.getPermissionGrantState(DAR, name, it.id)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
packagePermissions.value = emptyMap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fun setPackagePermission(name: String, permission: String, status: Int): Boolean {
|
fun setPackagePermission(name: String, permission: String, status: Int): Boolean {
|
||||||
val result = DPM.setPermissionGrantState(DAR, name, permission, status)
|
return DPM.setPermissionGrantState(DAR, name, permission, status)
|
||||||
getPackagePermissions(name)
|
}
|
||||||
return result
|
fun getPermissionPackages(permission: String): List<Pair<AppInfo, Int>> {
|
||||||
|
return PM.getInstalledPackages(
|
||||||
|
getInstalledAppsFlags or PackageManager.GET_PERMISSIONS
|
||||||
|
).filter {
|
||||||
|
it.requestedPermissions?.contains(permission) ?: false
|
||||||
|
}.map {
|
||||||
|
getAppInfo(it.packageName) to
|
||||||
|
DPM.getPermissionGrantState(DAR, it.packageName, permission)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metered data disabled packages
|
// Metered data disabled packages
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.bintianqi.owndroid
|
package com.bintianqi.owndroid
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
@@ -276,6 +275,3 @@ fun AppChooserScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val getInstalledAppsFlags =
|
|
||||||
if(Build.VERSION.SDK_INT >= 24) PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_UNINSTALLED_PACKAGES else 0
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@@ -179,3 +180,6 @@ fun registerPackageRemovedReceiver(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun parsePackageNames(input: String) = input.split('\n').filter { it.isNotEmpty() }
|
fun parsePackageNames(input: String) = input.split('\n').filter { it.isNotEmpty() }
|
||||||
|
|
||||||
|
val getInstalledAppsFlags =
|
||||||
|
if(Build.VERSION.SDK_INT >= 24) PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_UNINSTALLED_PACKAGES else 0
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
|
|||||||
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
|
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
|
||||||
import android.app.admin.PackagePolicy
|
import android.app.admin.PackagePolicy
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build.VERSION
|
import android.os.Build.VERSION
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
@@ -83,6 +84,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -223,7 +225,9 @@ fun ApplicationsFeaturesScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Un
|
|||||||
if(VERSION.SDK_INT >= 30 && (privilege.device || (VERSION.SDK_INT >= 33 && privilege.profile))) {
|
if(VERSION.SDK_INT >= 30 && (privilege.device || (VERSION.SDK_INT >= 33 && privilege.profile))) {
|
||||||
FunctionItem(R.string.disable_user_control, icon = R.drawable.do_not_touch_fill0) { onNavigate(DisableUserControl) }
|
FunctionItem(R.string.disable_user_control, icon = R.drawable.do_not_touch_fill0) { onNavigate(DisableUserControl) }
|
||||||
}
|
}
|
||||||
FunctionItem(R.string.permissions, icon = R.drawable.shield_fill0) { onNavigate(PermissionsManager()) }
|
FunctionItem(R.string.permissions, icon = R.drawable.shield_fill0) {
|
||||||
|
onNavigate(PermissionManager)
|
||||||
|
}
|
||||||
if(VERSION.SDK_INT >= 28) {
|
if(VERSION.SDK_INT >= 28) {
|
||||||
FunctionItem(R.string.disable_metered_data, icon = R.drawable.money_off_fill0) { onNavigate(DisableMeteredData) }
|
FunctionItem(R.string.disable_metered_data, icon = R.drawable.money_off_fill0) { onNavigate(DisableMeteredData) }
|
||||||
}
|
}
|
||||||
@@ -298,7 +302,7 @@ fun ApplicationDetailsScreen(
|
|||||||
.alpha(0.7F)
|
.alpha(0.7F)
|
||||||
.padding(bottom = 8.dp), style = typography.bodyMedium)
|
.padding(bottom = 8.dp), style = typography.bodyMedium)
|
||||||
}
|
}
|
||||||
FunctionItem(R.string.permissions, icon = R.drawable.shield_fill0) { onNavigate(PermissionsManager(packageName)) }
|
FunctionItem(R.string.permissions, icon = R.drawable.shield_fill0) { onNavigate(AppPermissionsManager(packageName)) }
|
||||||
if(VERSION.SDK_INT >= 24) SwitchItem(
|
if(VERSION.SDK_INT >= 24) SwitchItem(
|
||||||
R.string.suspend, icon = R.drawable.block_fill0, state = status.suspend,
|
R.string.suspend, icon = R.drawable.block_fill0, state = status.suspend,
|
||||||
onCheckedChange = { vm.adSetPackageSuspended(packageName, it) }
|
onCheckedChange = { vm.adSetPackageSuspended(packageName, it) }
|
||||||
@@ -353,40 +357,29 @@ fun ApplicationDetailsScreen(
|
|||||||
|
|
||||||
@Serializable object DisableUserControl
|
@Serializable object DisableUserControl
|
||||||
|
|
||||||
@Serializable data class PermissionsManager(val packageName: String? = null)
|
@Serializable data class AppPermissionsManager(val packageName: String)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PermissionsManagerScreen(
|
fun AppPermissionsManagerScreen(
|
||||||
packagePermissions: MutableStateFlow<Map<String, Int>>, getPackagePermissions: (String) -> Unit,
|
getPackagePermissions: (String) -> Map<String, Int>,
|
||||||
setPackagePermission: (String, String, Int) -> Boolean, onNavigateUp: () -> Unit,
|
setPackagePermission: (String, String, Int) -> Boolean, onNavigateUp: () -> Unit,
|
||||||
param: PermissionsManager, chosenPackage: Channel<String>, onChoosePackage: () -> Unit
|
param: AppPermissionsManager
|
||||||
) {
|
) {
|
||||||
val packageNameParam = param.packageName
|
val context = LocalContext.current
|
||||||
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||||
var packageName by rememberSaveable { mutableStateOf(packageNameParam ?: "") }
|
var selectedPermission by remember { mutableStateOf<PermissionItem?>(null) }
|
||||||
var selectedPermission by rememberSaveable { mutableIntStateOf(-1) }
|
val permissions = remember { mutableStateMapOf<String, Int>() }
|
||||||
val permissions by packagePermissions.collectAsStateWithLifecycle()
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
packageName = chosenPackage.receive()
|
permissions.putAll(getPackagePermissions(param.packageName))
|
||||||
}
|
|
||||||
LaunchedEffect(packageName) {
|
|
||||||
getPackagePermissions(packageName)
|
|
||||||
}
|
}
|
||||||
MyLazyScaffold(R.string.permissions, onNavigateUp) {
|
MyLazyScaffold(R.string.permissions, onNavigateUp) {
|
||||||
item {
|
items(runtimePermissions) {
|
||||||
if(packageNameParam == null) {
|
|
||||||
PackageNameTextField(packageName, onChoosePackage,
|
|
||||||
Modifier.padding(HorizontalPadding, 8.dp)) { packageName = it }
|
|
||||||
Spacer(Modifier.padding(vertical = 4.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
itemsIndexed(runtimePermissions, { _, it -> it.id }) { index, it ->
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
selectedPermission = index
|
selectedPermission = it
|
||||||
}
|
}
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
) {
|
) {
|
||||||
@@ -407,37 +400,50 @@ fun PermissionsManagerScreen(
|
|||||||
Spacer(Modifier.height(BottomPadding))
|
Spacer(Modifier.height(BottomPadding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(selectedPermission != -1) {
|
if(selectedPermission != null) PackagePermissionDialog(
|
||||||
val permission = runtimePermissions[selectedPermission]
|
selectedPermission!!, permissions[selectedPermission!!.id]!!, privilege.profile,
|
||||||
fun changeState(state: Int) {
|
{
|
||||||
val result = setPackagePermission(packageName, permission.id, state)
|
val result = setPackagePermission(param.packageName, selectedPermission!!.id, it)
|
||||||
if (result) selectedPermission = -1
|
if (!result) context.showOperationResultToast(false)
|
||||||
|
selectedPermission = null
|
||||||
|
permissions.putAll(getPackagePermissions(param.packageName))
|
||||||
}
|
}
|
||||||
|
) { selectedPermission = null }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PackagePermissionDialog(
|
||||||
|
permission: PermissionItem, currentState: Int, isProfileOwner: Boolean, onSet: (Int) -> Unit,
|
||||||
|
onClose: () -> Unit
|
||||||
|
) {
|
||||||
@Composable
|
@Composable
|
||||||
fun GrantPermissionItem(label: Int, status: Int) {
|
fun GrantPermissionItem(label: Int, stateId: Int) {
|
||||||
val selected = permissions[permission.id] == status
|
val selected = currentState == stateId
|
||||||
Row(
|
Row(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.background(if (selected) colorScheme.primaryContainer else Color.Transparent)
|
.background(if (selected) colorScheme.primaryContainer else Color.Transparent)
|
||||||
.clickable { changeState(status) }
|
.clickable { onSet(stateId) }
|
||||||
.padding(vertical = 16.dp, horizontal = 12.dp),
|
.padding(vertical = 16.dp, horizontal = 12.dp),
|
||||||
Arrangement.SpaceBetween, Alignment.CenterVertically,
|
Arrangement.SpaceBetween, Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(stringResource(label), color = if(selected) colorScheme.primary else Color.Unspecified)
|
Text(
|
||||||
if(selected) Icon(Icons.Outlined.CheckCircle, null, tint = colorScheme.primary)
|
stringResource(label),
|
||||||
|
color = if(selected) colorScheme.primary else Color.Unspecified
|
||||||
|
)
|
||||||
|
if (selected) Icon(Icons.Outlined.CheckCircle, null, tint = colorScheme.primary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { selectedPermission = -1 },
|
onDismissRequest = onClose,
|
||||||
confirmButton = { TextButton({ selectedPermission = -1 }) { Text(stringResource(R.string.cancel)) } },
|
confirmButton = { TextButton(onClose) { Text(stringResource(R.string.cancel)) } },
|
||||||
title = { Text(stringResource(permission.label)) },
|
title = { Text(stringResource(permission.label)) },
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
Text(permission.id)
|
Text(permission.id)
|
||||||
Spacer(Modifier.padding(vertical = 4.dp))
|
Spacer(Modifier.padding(vertical = 4.dp))
|
||||||
if(!(VERSION.SDK_INT >= 31 && permission.profileOwnerRestricted && privilege.profile)) {
|
if(!(VERSION.SDK_INT >= 31 && permission.profileOwnerRestricted && isProfileOwner)) {
|
||||||
GrantPermissionItem(R.string.granted, PERMISSION_GRANT_STATE_GRANTED)
|
GrantPermissionItem(R.string.granted, PERMISSION_GRANT_STATE_GRANTED)
|
||||||
}
|
}
|
||||||
GrantPermissionItem(R.string.denied, PERMISSION_GRANT_STATE_DENIED)
|
GrantPermissionItem(R.string.denied, PERMISSION_GRANT_STATE_DENIED)
|
||||||
@@ -445,7 +451,139 @@ fun PermissionsManagerScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable object PermissionManager
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PermissionManagerScreen(onNavigate: (PermissionDetail) -> Unit, onNavigateUp: () -> Unit) {
|
||||||
|
MyLazyScaffold(R.string.permissions, onNavigateUp) {
|
||||||
|
items(runtimePermissions) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
onNavigate(PermissionDetail(it.id))
|
||||||
}
|
}
|
||||||
|
.padding(8.dp, 12.dp)
|
||||||
|
) {
|
||||||
|
Icon(painterResource(it.icon), null, Modifier.padding(horizontal = 12.dp))
|
||||||
|
Text(stringResource(it.label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(Modifier.height(BottomPadding))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable class PermissionDetail(val permission: String)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun PermissionDetailScreen(
|
||||||
|
param: PermissionDetail, getPermissionPackages: (String) -> List<Pair<AppInfo, Int>>,
|
||||||
|
setPackagePermission: (String, String, Int) -> Boolean, onNavigateUp: () -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||||
|
val permissionItem = runtimePermissions.find { it.id == param.permission }!!
|
||||||
|
val packagesList = remember { mutableStateListOf<Pair<AppInfo, Int>>() }
|
||||||
|
var selectedPackage by remember { mutableStateOf<Pair<String, Int>?>(null) }
|
||||||
|
var showUserApps by remember { mutableStateOf(true) }
|
||||||
|
var showSystemApps by remember { mutableStateOf(false) }
|
||||||
|
val displayedPackagesList = packagesList.filter {
|
||||||
|
(showUserApps && it.first.flags and ApplicationInfo.FLAG_SYSTEM == 0) ||
|
||||||
|
(showSystemApps && it.first.flags and ApplicationInfo.FLAG_SYSTEM != 0)
|
||||||
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
packagesList.addAll(getPermissionPackages(param.permission))
|
||||||
|
}
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
{ Text(stringResource(permissionItem.label)) },
|
||||||
|
navigationIcon = { NavIcon(onNavigateUp) },
|
||||||
|
actions = {
|
||||||
|
var menu by remember { mutableStateOf(false) }
|
||||||
|
Box {
|
||||||
|
IconButton({ menu = true }) {
|
||||||
|
Icon(painterResource(R.drawable.filter_alt_fill0), null)
|
||||||
|
}
|
||||||
|
DropdownMenu(menu, { menu = false }) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
{
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Checkbox(showUserApps, { showUserApps = it })
|
||||||
|
Text(stringResource(R.string.user_apps))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ showUserApps = !showUserApps }
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
{
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Checkbox(showSystemApps, { showSystemApps = it })
|
||||||
|
Text(stringResource(R.string.system_apps))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ showSystemApps = !showSystemApps }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
contentWindowInsets = adaptiveInsets()
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(Modifier.padding(paddingValues)) {
|
||||||
|
items(displayedPackagesList) { (info, grantState) ->
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { selectedPackage = info.name to grantState }
|
||||||
|
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||||
|
Arrangement.SpaceBetween, Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Row(Modifier.weight(1F), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Image(
|
||||||
|
rememberDrawablePainter(info.icon), null,
|
||||||
|
Modifier
|
||||||
|
.padding(start = 12.dp, end = 18.dp)
|
||||||
|
.size(30.dp)
|
||||||
|
)
|
||||||
|
Column {
|
||||||
|
Text(info.label)
|
||||||
|
Text(info.name, Modifier.alpha(0.8F), style = typography.bodyMedium)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (grantState != 0) {
|
||||||
|
Icon(
|
||||||
|
painterResource(
|
||||||
|
if (grantState == 1) R.drawable.check_circle_fill0
|
||||||
|
else R.drawable.cancel_fill0
|
||||||
|
),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(Modifier.height(BottomPadding))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedPackage != null) PackagePermissionDialog(
|
||||||
|
permissionItem, selectedPackage!!.second, privilege.profile,
|
||||||
|
{
|
||||||
|
val result = setPackagePermission(selectedPackage!!.first, param.permission, it)
|
||||||
|
if (!result) context.showOperationResultToast(false)
|
||||||
|
selectedPackage = null
|
||||||
|
packagesList.clear()
|
||||||
|
packagesList.addAll(getPermissionPackages(param.permission))
|
||||||
|
}
|
||||||
|
) { selectedPackage = null }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable object DisableMeteredData
|
@Serializable object DisableMeteredData
|
||||||
|
|||||||
9
app/src/main/res/drawable/cancel_fill0.xml
Normal file
9
app/src/main/res/drawable/cancel_fill0.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:pathData="m336,680 l144,-144 144,144 56,-56 -144,-144 144,-144 -56,-56 -144,144 -144,-144 -56,56 144,144 -144,144 56,56ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,800q134,0 227,-93t93,-227q0,-134 -93,-227t-227,-93q-134,0 -227,93t-93,227q0,134 93,227t227,93ZM480,480Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
||||||
@@ -359,6 +359,8 @@
|
|||||||
<string name="enable_lockdown">启用锁定</string>
|
<string name="enable_lockdown">启用锁定</string>
|
||||||
<string name="clear_current_config">清除当前配置</string>
|
<string name="clear_current_config">清除当前配置</string>
|
||||||
<string name="permissions">权限</string>
|
<string name="permissions">权限</string>
|
||||||
|
<string name="user_apps">用户应用</string>
|
||||||
|
<string name="system_apps">系统应用</string>
|
||||||
<string name="not_installed">未安装</string>
|
<string name="not_installed">未安装</string>
|
||||||
<string name="block_uninstall">阻止卸载</string>
|
<string name="block_uninstall">阻止卸载</string>
|
||||||
<string name="disable_user_control">禁止用户控制</string>
|
<string name="disable_user_control">禁止用户控制</string>
|
||||||
|
|||||||
@@ -393,6 +393,8 @@
|
|||||||
<string name="enable_lockdown">Enable lockdown</string>
|
<string name="enable_lockdown">Enable lockdown</string>
|
||||||
<string name="clear_current_config">Clear current config</string>
|
<string name="clear_current_config">Clear current config</string>
|
||||||
<string name="permissions">Permissions</string>
|
<string name="permissions">Permissions</string>
|
||||||
|
<string name="user_apps">User apps</string>
|
||||||
|
<string name="system_apps">System apps</string>
|
||||||
<string name="not_installed">Not installed</string>
|
<string name="not_installed">Not installed</string>
|
||||||
<string name="block_uninstall">Block uninstall</string>
|
<string name="block_uninstall">Block uninstall</string>
|
||||||
<string name="enable_system_app">Enable system app</string>
|
<string name="enable_system_app">Enable system app</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user