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 {
|
||||
includeInApk = false
|
||||
}
|
||||
composeCompiler {
|
||||
includeSourceInformation = false
|
||||
includeTraceMarkers = false
|
||||
}
|
||||
}
|
||||
|
||||
composeCompiler {
|
||||
includeSourceInformation = false
|
||||
includeTraceMarkers = false
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
||||
@@ -7,12 +7,9 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -50,10 +47,7 @@ class LockTaskService: Service() {
|
||||
.setOngoing(true)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
.build()
|
||||
ServiceCompat.startForeground(
|
||||
this, NotificationType.LockTaskMode.id, notification,
|
||||
if (Build.VERSION.SDK_INT < 34) 0 else ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
|
||||
)
|
||||
startForeground(NotificationType.LockTaskMode.id, notification)
|
||||
coroutineScope.launch {
|
||||
val am = getSystemService(ActivityManager::class.java)
|
||||
delay(3000)
|
||||
|
||||
@@ -165,8 +165,12 @@ import com.bintianqi.owndroid.dpm.PasswordInfoScreen
|
||||
import com.bintianqi.owndroid.dpm.PasswordScreen
|
||||
import com.bintianqi.owndroid.dpm.PermissionPolicy
|
||||
import com.bintianqi.owndroid.dpm.PermissionPolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.PermissionsManager
|
||||
import com.bintianqi.owndroid.dpm.PermissionsManagerScreen
|
||||
import com.bintianqi.owndroid.dpm.AppPermissionsManager
|
||||
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.PermittedAsAndImPackages
|
||||
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
|
||||
)
|
||||
}
|
||||
composable<PermissionsManager> {
|
||||
PermissionsManagerScreen(
|
||||
vm.packagePermissions, vm::getPackagePermissions, vm::setPackagePermission,
|
||||
::navigateUp, it.toRoute(), vm.chosenPackage, ::chooseSinglePackage
|
||||
composable<AppPermissionsManager> {
|
||||
AppPermissionsManagerScreen(
|
||||
vm::getPackagePermissions, vm::setPackagePermission, ::navigateUp, it.toRoute()
|
||||
)
|
||||
}
|
||||
composable<PermissionManager> {
|
||||
PermissionManagerScreen(::navigate, ::navigateUp)
|
||||
}
|
||||
composable<PermissionDetail> {
|
||||
PermissionDetailScreen(
|
||||
it.toRoute(), vm::getPermissionPackages, vm::setPackagePermission, ::navigateUp
|
||||
)
|
||||
}
|
||||
composable<DisableMeteredData> {
|
||||
|
||||
@@ -272,6 +272,6 @@ class MyRepository(val dbHelper: MyDbHelper) {
|
||||
return list
|
||||
}
|
||||
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.globalSettings
|
||||
import com.bintianqi.owndroid.dpm.handlePrivilegeChange
|
||||
import com.bintianqi.owndroid.dpm.isValidPackageName
|
||||
import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage
|
||||
import com.bintianqi.owndroid.dpm.runtimePermissions
|
||||
import com.bintianqi.owndroid.dpm.secureSettings
|
||||
@@ -280,20 +279,23 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
getUcdPackages()
|
||||
}
|
||||
|
||||
val packagePermissions = MutableStateFlow(emptyMap<String, Int>())
|
||||
fun getPackagePermissions(name: String) {
|
||||
if (name.isValidPackageName) {
|
||||
packagePermissions.value = runtimePermissions.associate {
|
||||
it.id to DPM.getPermissionGrantState(DAR, name, it.id)
|
||||
}
|
||||
} else {
|
||||
packagePermissions.value = emptyMap()
|
||||
fun getPackagePermissions(name: String): Map<String, Int> {
|
||||
return runtimePermissions.associate {
|
||||
it.id to DPM.getPermissionGrantState(DAR, name, it.id)
|
||||
}
|
||||
}
|
||||
fun setPackagePermission(name: String, permission: String, status: Int): Boolean {
|
||||
val result = DPM.setPermissionGrantState(DAR, name, permission, status)
|
||||
getPackagePermissions(name)
|
||||
return result
|
||||
return DPM.setPermissionGrantState(DAR, name, permission, status)
|
||||
}
|
||||
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
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.bintianqi.owndroid
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
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.IntentFilter
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
@@ -179,3 +180,6 @@ fun registerPackageRemovedReceiver(
|
||||
}
|
||||
|
||||
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.PackagePolicy
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.net.Uri
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Looper
|
||||
@@ -83,6 +84,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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))) {
|
||||
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) {
|
||||
FunctionItem(R.string.disable_metered_data, icon = R.drawable.money_off_fill0) { onNavigate(DisableMeteredData) }
|
||||
}
|
||||
@@ -298,7 +302,7 @@ fun ApplicationDetailsScreen(
|
||||
.alpha(0.7F)
|
||||
.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(
|
||||
R.string.suspend, icon = R.drawable.block_fill0, state = status.suspend,
|
||||
onCheckedChange = { vm.adSetPackageSuspended(packageName, it) }
|
||||
@@ -353,40 +357,29 @@ fun ApplicationDetailsScreen(
|
||||
|
||||
@Serializable object DisableUserControl
|
||||
|
||||
@Serializable data class PermissionsManager(val packageName: String? = null)
|
||||
@Serializable data class AppPermissionsManager(val packageName: String)
|
||||
|
||||
@Composable
|
||||
fun PermissionsManagerScreen(
|
||||
packagePermissions: MutableStateFlow<Map<String, Int>>, getPackagePermissions: (String) -> Unit,
|
||||
fun AppPermissionsManagerScreen(
|
||||
getPackagePermissions: (String) -> Map<String, Int>,
|
||||
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()
|
||||
var packageName by rememberSaveable { mutableStateOf(packageNameParam ?: "") }
|
||||
var selectedPermission by rememberSaveable { mutableIntStateOf(-1) }
|
||||
val permissions by packagePermissions.collectAsStateWithLifecycle()
|
||||
var selectedPermission by remember { mutableStateOf<PermissionItem?>(null) }
|
||||
val permissions = remember { mutableStateMapOf<String, Int>() }
|
||||
LaunchedEffect(Unit) {
|
||||
packageName = chosenPackage.receive()
|
||||
}
|
||||
LaunchedEffect(packageName) {
|
||||
getPackagePermissions(packageName)
|
||||
permissions.putAll(getPackagePermissions(param.packageName))
|
||||
}
|
||||
MyLazyScaffold(R.string.permissions, onNavigateUp) {
|
||||
item {
|
||||
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 ->
|
||||
items(runtimePermissions) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
selectedPermission = index
|
||||
selectedPermission = it
|
||||
}
|
||||
.padding(8.dp)
|
||||
) {
|
||||
@@ -407,45 +400,190 @@ fun PermissionsManagerScreen(
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
}
|
||||
if(selectedPermission != -1) {
|
||||
val permission = runtimePermissions[selectedPermission]
|
||||
fun changeState(state: Int) {
|
||||
val result = setPackagePermission(packageName, permission.id, state)
|
||||
if (result) selectedPermission = -1
|
||||
if(selectedPermission != null) PackagePermissionDialog(
|
||||
selectedPermission!!, permissions[selectedPermission!!.id]!!, privilege.profile,
|
||||
{
|
||||
val result = setPackagePermission(param.packageName, selectedPermission!!.id, it)
|
||||
if (!result) context.showOperationResultToast(false)
|
||||
selectedPermission = null
|
||||
permissions.putAll(getPackagePermissions(param.packageName))
|
||||
}
|
||||
@Composable
|
||||
fun GrantPermissionItem(label: Int, status: Int) {
|
||||
val selected = permissions[permission.id] == status
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(if (selected) colorScheme.primaryContainer else Color.Transparent)
|
||||
.clickable { changeState(status) }
|
||||
.padding(vertical = 16.dp, horizontal = 12.dp),
|
||||
Arrangement.SpaceBetween, Alignment.CenterVertically,
|
||||
) {
|
||||
Text(stringResource(label), color = if(selected) colorScheme.primary else Color.Unspecified)
|
||||
if(selected) Icon(Icons.Outlined.CheckCircle, null, tint = colorScheme.primary)
|
||||
) { selectedPermission = null }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PackagePermissionDialog(
|
||||
permission: PermissionItem, currentState: Int, isProfileOwner: Boolean, onSet: (Int) -> Unit,
|
||||
onClose: () -> Unit
|
||||
) {
|
||||
@Composable
|
||||
fun GrantPermissionItem(label: Int, stateId: Int) {
|
||||
val selected = currentState == stateId
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(if (selected) colorScheme.primaryContainer else Color.Transparent)
|
||||
.clickable { onSet(stateId) }
|
||||
.padding(vertical = 16.dp, horizontal = 12.dp),
|
||||
Arrangement.SpaceBetween, Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
stringResource(label),
|
||||
color = if(selected) colorScheme.primary else Color.Unspecified
|
||||
)
|
||||
if (selected) Icon(Icons.Outlined.CheckCircle, null, tint = colorScheme.primary)
|
||||
}
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = onClose,
|
||||
confirmButton = { TextButton(onClose) { Text(stringResource(R.string.cancel)) } },
|
||||
title = { Text(stringResource(permission.label)) },
|
||||
text = {
|
||||
Column {
|
||||
Text(permission.id)
|
||||
Spacer(Modifier.padding(vertical = 4.dp))
|
||||
if(!(VERSION.SDK_INT >= 31 && permission.profileOwnerRestricted && isProfileOwner)) {
|
||||
GrantPermissionItem(R.string.granted, PERMISSION_GRANT_STATE_GRANTED)
|
||||
}
|
||||
GrantPermissionItem(R.string.denied, PERMISSION_GRANT_STATE_DENIED)
|
||||
GrantPermissionItem(R.string.default_stringres, PERMISSION_GRANT_STATE_DEFAULT)
|
||||
}
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = { selectedPermission = -1 },
|
||||
confirmButton = { TextButton({ selectedPermission = -1 }) { Text(stringResource(R.string.cancel)) } },
|
||||
title = { Text(stringResource(permission.label)) },
|
||||
text = {
|
||||
Column {
|
||||
Text(permission.id)
|
||||
Spacer(Modifier.padding(vertical = 4.dp))
|
||||
if(!(VERSION.SDK_INT >= 31 && permission.profileOwnerRestricted && privilege.profile)) {
|
||||
GrantPermissionItem(R.string.granted, PERMISSION_GRANT_STATE_GRANTED)
|
||||
)
|
||||
}
|
||||
|
||||
@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
|
||||
)
|
||||
}
|
||||
GrantPermissionItem(R.string.denied, PERMISSION_GRANT_STATE_DENIED)
|
||||
GrantPermissionItem(R.string.default_stringres, PERMISSION_GRANT_STATE_DEFAULT)
|
||||
}
|
||||
}
|
||||
)
|
||||
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
|
||||
|
||||
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="clear_current_config">清除当前配置</string>
|
||||
<string name="permissions">权限</string>
|
||||
<string name="user_apps">用户应用</string>
|
||||
<string name="system_apps">系统应用</string>
|
||||
<string name="not_installed">未安装</string>
|
||||
<string name="block_uninstall">阻止卸载</string>
|
||||
<string name="disable_user_control">禁止用户控制</string>
|
||||
|
||||
@@ -393,6 +393,8 @@
|
||||
<string name="enable_lockdown">Enable lockdown</string>
|
||||
<string name="clear_current_config">Clear current config</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="block_uninstall">Block uninstall</string>
|
||||
<string name="enable_system_app">Enable system app</string>
|
||||
|
||||
Reference in New Issue
Block a user