Implement Managed configurations (#198)

This commit is contained in:
BinTianqi
2025-11-24 18:01:49 +08:00
parent d375a9bae6
commit 4bcd2d8150
11 changed files with 450 additions and 1 deletions

View File

@@ -137,6 +137,8 @@ import com.bintianqi.owndroid.dpm.LockTaskMode
import com.bintianqi.owndroid.dpm.LockTaskModeScreen import com.bintianqi.owndroid.dpm.LockTaskModeScreen
import com.bintianqi.owndroid.dpm.ManageAppGroups import com.bintianqi.owndroid.dpm.ManageAppGroups
import com.bintianqi.owndroid.dpm.ManageAppGroupsScreen import com.bintianqi.owndroid.dpm.ManageAppGroupsScreen
import com.bintianqi.owndroid.dpm.ManagedConfiguration
import com.bintianqi.owndroid.dpm.ManagedConfigurationScreen
import com.bintianqi.owndroid.dpm.MtePolicy import com.bintianqi.owndroid.dpm.MtePolicy
import com.bintianqi.owndroid.dpm.MtePolicyScreen import com.bintianqi.owndroid.dpm.MtePolicyScreen
import com.bintianqi.owndroid.dpm.NearbyStreamingPolicy import com.bintianqi.owndroid.dpm.NearbyStreamingPolicy
@@ -598,6 +600,12 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
composable<SetDefaultDialer> { composable<SetDefaultDialer> {
SetDefaultDialerScreen(vm.chosenPackage, ::choosePackage, vm::setDefaultDialer, ::navigateUp) SetDefaultDialerScreen(vm.chosenPackage, ::choosePackage, vm::setDefaultDialer, ::navigateUp)
} }
composable<ManagedConfiguration> {
ManagedConfigurationScreen(
it.toRoute(), vm.appRestrictions, vm::getAppRestrictions, vm::setAppRestrictions,
vm::clearAppRestrictions, ::navigateUp
)
}
composable<ManageAppGroups> { composable<ManageAppGroups> {
ManageAppGroupsScreen( ManageAppGroupsScreen(
vm.appGroups, vm.appGroups,

View File

@@ -25,6 +25,8 @@ import android.content.ComponentName
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.RestrictionEntry
import android.content.RestrictionsManager
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller
import android.content.pm.PackageManager import android.content.pm.PackageManager
@@ -39,6 +41,7 @@ import android.net.wifi.WifiManager
import android.net.wifi.WifiSsid import android.net.wifi.WifiSsid
import android.os.Binder import android.os.Binder
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Bundle
import android.os.HardwarePropertiesManager import android.os.HardwarePropertiesManager
import android.os.UserHandle import android.os.UserHandle
import android.os.UserManager import android.os.UserManager
@@ -60,6 +63,7 @@ import com.bintianqi.owndroid.dpm.ApnConfig
import com.bintianqi.owndroid.dpm.ApnMvnoType import com.bintianqi.owndroid.dpm.ApnMvnoType
import com.bintianqi.owndroid.dpm.ApnProtocol import com.bintianqi.owndroid.dpm.ApnProtocol
import com.bintianqi.owndroid.dpm.AppGroup import com.bintianqi.owndroid.dpm.AppGroup
import com.bintianqi.owndroid.dpm.AppRestriction
import com.bintianqi.owndroid.dpm.AppStatus import com.bintianqi.owndroid.dpm.AppStatus
import com.bintianqi.owndroid.dpm.CaCertInfo import com.bintianqi.owndroid.dpm.CaCertInfo
import com.bintianqi.owndroid.dpm.CreateUserResult import com.bintianqi.owndroid.dpm.CreateUserResult
@@ -510,6 +514,79 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
} }
val appRestrictions = MutableStateFlow(emptyList<AppRestriction>())
@RequiresApi(23)
fun getAppRestrictions(name: String) {
val rm = application.getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager
val bundle = DPM.getApplicationRestrictions(DAR, name)
println(bundle.keySet())
appRestrictions.value = rm.getManifestRestrictions(name).mapNotNull {
transformRestrictionEntry(it)
}.map {
if (bundle.containsKey(it.key)) {
when (it) {
is AppRestriction.BooleanItem -> it.value = bundle.getBoolean(it.key)
is AppRestriction.StringItem -> it.value = bundle.getString(it.key)
is AppRestriction.IntItem -> it.value = bundle.getInt(it.key)
is AppRestriction.ChoiceItem -> it.value = bundle.getString(it.key)
is AppRestriction.MultiSelectItem -> it.value = bundle.getStringArray(it.key)
}
}
it
}
}
@RequiresApi(23)
fun setAppRestrictions(name: String, item: AppRestriction) {
viewModelScope.launch(Dispatchers.IO) {
appRestrictions.value = emptyList()
DPM.setApplicationRestrictions(
DAR, name,
transformAppRestriction(appRestrictions.value.filter { it.key != item.key }.plus(item))
)
getAppRestrictions(name)
}
}
@RequiresApi(23)
fun clearAppRestrictions(name: String) {
viewModelScope.launch(Dispatchers.IO) {
DPM.setApplicationRestrictions(DAR, name, Bundle())
getAppRestrictions(name)
}
}
fun transformRestrictionEntry(e: RestrictionEntry): AppRestriction? {
return when (e.type) {
RestrictionEntry.TYPE_INTEGER ->
AppRestriction.IntItem(e.key, e.title, e.description, null)
RestrictionEntry.TYPE_STRING ->
AppRestriction.StringItem(e.key, e.title, e.description, null)
RestrictionEntry.TYPE_BOOLEAN ->
AppRestriction.BooleanItem(e.key, e.title, e.description, null)
RestrictionEntry.TYPE_CHOICE -> AppRestriction.ChoiceItem(e.key, e.title,
e.description, e.choiceEntries, e.choiceValues, null)
RestrictionEntry.TYPE_MULTI_SELECT -> AppRestriction.MultiSelectItem(e.key, e.title,
e.description, e.choiceEntries, e.choiceValues, null)
else -> null
}
}
fun transformAppRestriction(list: List<AppRestriction>): Bundle {
val b = Bundle()
for (r in list) {
when (r) {
is AppRestriction.IntItem -> r.value?.let { b.putInt(r.key, it) }
is AppRestriction.StringItem -> r.value?.let { b.putString(r.key, it) }
is AppRestriction.BooleanItem -> r.value?.let { b.putBoolean(r.key, it) }
is AppRestriction.ChoiceItem -> r.value?.let { b.putString(r.key, it) }
is AppRestriction.MultiSelectItem -> r.value?.let { b.putStringArray(r.key, r.value) }
}
}
return b
}
val appGroups = MutableStateFlow(emptyList<AppGroup>()) val appGroups = MutableStateFlow(emptyList<AppGroup>())
init { init {
getAppGroups() getAppGroups()

View File

@@ -232,7 +232,7 @@ fun AppLockSettingsScreen(
config: AppLockConfig, setConfig: (AppLockConfig) -> Unit, config: AppLockConfig, setConfig: (AppLockConfig) -> Unit,
onNavigateUp: () -> Unit onNavigateUp: () -> Unit
) = MyScaffold(R.string.app_lock, onNavigateUp) { ) = MyScaffold(R.string.app_lock, onNavigateUp) {
var password by rememberSaveable { mutableStateOf(config.password ?: "") } var password by rememberSaveable { mutableStateOf("") }
var confirmPassword by rememberSaveable { mutableStateOf("") } var confirmPassword by rememberSaveable { mutableStateOf("") }
var allowBiometrics by rememberSaveable { mutableStateOf(config.biometrics) } var allowBiometrics by rememberSaveable { mutableStateOf(config.biometrics) }
var lockWhenLeaving by rememberSaveable { mutableStateOf(config.whenLeaving) } var lockWhenLeaving by rememberSaveable { mutableStateOf(config.whenLeaving) }

View File

@@ -14,6 +14,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -21,6 +22,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
@@ -29,6 +31,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.List import androidx.compose.material.icons.automirrored.filled.List
@@ -39,8 +42,10 @@ import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.CheckCircle import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@@ -53,7 +58,10 @@ import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
@@ -77,9 +85,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.AppInfo import com.bintianqi.owndroid.AppInfo
@@ -285,6 +295,11 @@ fun ApplicationDetailsScreen(
state = status.keepUninstalled, state = status.keepUninstalled,
onCheckedChange = { vm.adSetPackageKu(packageName, it) } onCheckedChange = { vm.adSetPackageKu(packageName, it) }
) )
if (VERSION.SDK_INT >= 23) {
FunctionItem(R.string.managed_configuration, icon = R.drawable.description_fill0) {
onNavigate(ManagedConfiguration(packageName))
}
}
if(VERSION.SDK_INT >= 28) FunctionItem(R.string.clear_app_storage, icon = R.drawable.mop_fill0) { dialog = 1 } if(VERSION.SDK_INT >= 28) FunctionItem(R.string.clear_app_storage, icon = R.drawable.mop_fill0) { dialog = 1 }
FunctionItem(R.string.uninstall, icon = R.drawable.delete_fill0) { dialog = 2 } FunctionItem(R.string.uninstall, icon = R.drawable.delete_fill0) { dialog = 2 }
Spacer(Modifier.height(BottomPadding)) Spacer(Modifier.height(BottomPadding))
@@ -981,3 +996,301 @@ fun EditAppGroupScreen(
} }
} }
} }
@Serializable class ManagedConfiguration(val packageName: String)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ManagedConfigurationScreen(
params: ManagedConfiguration, appRestrictions: StateFlow<List<AppRestriction>>,
getRestriction: (String) -> Unit, setRestriction: (String, AppRestriction) -> Unit,
clearRestriction: (String) -> Unit, navigateUp: () -> Unit
) {
val restrictions by appRestrictions.collectAsStateWithLifecycle()
var dialog by remember { mutableIntStateOf(-1) }
var clearRestrictionDialog by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
getRestriction(params.packageName)
}
Scaffold(
topBar = {
TopAppBar(
{ Text(stringResource(R.string.managed_configuration)) },
navigationIcon = { NavIcon(navigateUp) },
actions = {
IconButton({
clearRestrictionDialog = true
}) {
Icon(Icons.Outlined.Delete, null)
}
}
)
}
) { paddingValues ->
LazyColumn(Modifier.padding(paddingValues)) {
itemsIndexed(restrictions) { index, entry ->
Row(
Modifier.fillMaxWidth().clickable {
dialog = index
}.padding(HorizontalPadding, 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
val iconId = when (entry) {
is AppRestriction.IntItem -> R.drawable.number_123_fill0
is AppRestriction.StringItem -> R.drawable.abc_fill0
is AppRestriction.BooleanItem -> R.drawable.toggle_off_fill0
is AppRestriction.ChoiceItem -> R.drawable.radio_button_checked_fill0
is AppRestriction.MultiSelectItem -> R.drawable.check_box_fill0
}
Icon(painterResource(iconId), null, Modifier.padding(end = 12.dp))
Column {
if (entry.title != null) {
Text(entry.title!!, style = typography.labelLarge)
Text(entry.key, style = typography.bodyMedium)
} else {
Text(entry.key, style = typography.labelLarge)
}
val text = when (entry) {
is AppRestriction.IntItem -> entry.value?.toString()
is AppRestriction.StringItem -> entry.value?.take(30)
is AppRestriction.BooleanItem -> entry.value?.toString()
is AppRestriction.ChoiceItem -> entry.value
is AppRestriction.MultiSelectItem -> entry.value?.joinToString(limit = 30)
}
Text(
text ?: "null", Modifier.alpha(0.7F),
fontStyle = if(text == null) FontStyle.Italic else null,
style = typography.bodyMedium
)
}
}
}
item {
Spacer(Modifier.height(BottomPadding))
}
}
}
if (dialog != -1) Dialog({
dialog = -1
}) {
Surface(
color = AlertDialogDefaults.containerColor,
shape = AlertDialogDefaults.shape,
tonalElevation = AlertDialogDefaults.TonalElevation,
) {
Column(Modifier.verticalScroll(rememberScrollState()).padding(12.dp)) {
ManagedConfigurationDialog(restrictions[dialog]) {
if (it != null) {
setRestriction(params.packageName, it)
}
dialog = -1
}
}
}
}
if (clearRestrictionDialog) AlertDialog(
text = {
Text(stringResource(R.string.clear_configurations))
},
confirmButton = {
TextButton({
clearRestriction(params.packageName)
clearRestrictionDialog = false
}) {
Text(stringResource(R.string.confirm))
}
},
dismissButton = {
TextButton({
clearRestrictionDialog = false
}) {
Text(stringResource(R.string.cancel))
}
},
onDismissRequest = {
clearRestrictionDialog = false
}
)
}
@Composable
fun ColumnScope.ManagedConfigurationDialog(
restriction: AppRestriction, setRestriction: (AppRestriction?) -> Unit
) {
var specifyValue by remember { mutableStateOf(false) }
var input by remember { mutableStateOf("") }
var inputState by remember { mutableStateOf(false) }
val inputSelections = remember { mutableStateListOf<String>() }
LaunchedEffect(Unit) {
when (restriction) {
is AppRestriction.IntItem -> restriction.value?.let {
input = it.toString()
specifyValue = true
}
is AppRestriction.StringItem -> restriction.value?.let {
input = it
specifyValue = true
}
is AppRestriction.BooleanItem -> restriction.value?.let {
inputState = it
specifyValue = true
}
is AppRestriction.ChoiceItem -> restriction.value?.let {
input = it
specifyValue = true
}
is AppRestriction.MultiSelectItem -> restriction.value?.let {
inputSelections.addAll(it)
specifyValue = true
}
}
}
SelectionContainer {
Column {
restriction.title?.let {
Text(it, style = typography.titleLarge)
}
Text(restriction.key, Modifier.padding(vertical = 4.dp), style = typography.labelLarge)
Spacer(Modifier.height(4.dp))
restriction.description?.let {
Text(it, Modifier.alpha(0.8F), style = typography.bodyMedium)
}
Spacer(Modifier.height(8.dp))
}
}
Row(
Modifier.fillMaxWidth().padding(bottom = 4.dp),
Arrangement.SpaceBetween, Alignment.CenterVertically
) {
Text(stringResource(R.string.specify_value))
Switch(specifyValue, { specifyValue = it })
}
if (specifyValue) when (restriction) {
is AppRestriction.IntItem -> {
OutlinedTextField(
input, { input = it }, Modifier.fillMaxWidth(),
isError = input.toIntOrNull() == null
)
}
is AppRestriction.StringItem -> {
OutlinedTextField(
input, { input = it }, Modifier.fillMaxWidth()
)
}
is AppRestriction.BooleanItem -> {
Switch(inputState, { inputState = it })
}
is AppRestriction.ChoiceItem -> {
restriction.entryValues.forEachIndexed { index, value ->
val label = restriction.entries.getOrNull(index)
Row(
Modifier.fillMaxWidth().clickable {
input = value
}.padding(8.dp, 4.dp)
) {
RadioButton(input == value, { input = value })
Spacer(Modifier.width(8.dp))
if (label == null) {
Text(value)
} else {
Column {
Text(label)
Text(value, Modifier.alpha(0.7F), style = typography.bodyMedium)
}
}
}
}
}
is AppRestriction.MultiSelectItem -> {
restriction.entryValues.forEachIndexed { index, value ->
val label = restriction.entries.getOrNull(index)
Row(
Modifier.fillMaxWidth().clickable {
if (value in inputSelections)
inputSelections -= value else inputSelections += value
}.padding(8.dp, 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(value in inputSelections, null)
Spacer(Modifier.width(8.dp))
if (label == null) {
Text(value)
} else {
Column {
Text(label)
Text(value, Modifier.alpha(0.7F), style = typography.bodyMedium)
}
}
}
}
}
}
Row(Modifier.align(Alignment.End).padding(top = 4.dp)) {
TextButton({
setRestriction(null)
}, Modifier.padding(end = 4.dp)) {
Text(stringResource(R.string.cancel))
}
TextButton({
val newRestriction = when (restriction) {
is AppRestriction.IntItem -> restriction.copy(
value = if (specifyValue) input.toIntOrNull() else null
)
is AppRestriction.StringItem -> restriction.copy(
value = if (specifyValue) input else null
)
is AppRestriction.BooleanItem -> restriction.copy(
value = if (specifyValue) inputState else null
)
is AppRestriction.ChoiceItem -> restriction.copy(
value = if (specifyValue) input else null
)
is AppRestriction.MultiSelectItem -> restriction.copy(
value = if (specifyValue) inputSelections.toTypedArray() else null
)
}
setRestriction(newRestriction)
}) {
Text(stringResource(R.string.confirm))
}
}
}
sealed class AppRestriction(
open val key: String, open val title: String?, open val description: String?
) {
data class IntItem(
override val key: String,
override val title: String?,
override val description: String?,
var value: Int?,
) : AppRestriction(key, title, description)
data class StringItem(
override val key: String,
override val title: String?,
override val description: String?,
var value: String?
) : AppRestriction(key, title, description)
data class BooleanItem(
override val key: String,
override val title: String?,
override val description: String?,
var value: Boolean?
) : AppRestriction(key, title, description)
data class ChoiceItem(
override val key: String,
override val title: String?,
override val description: String?,
val entries: Array<String>,
val entryValues: Array<String>,
var value: String?
) : AppRestriction(key, title, description)
data class MultiSelectItem(
override val key: String,
override val title: String?,
override val description: String?,
val entries: Array<String>,
val entryValues: Array<String>,
var value: Array<String>?
) : AppRestriction(key, title, description)
}

View 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="M680,600q-17,0 -28.5,-11.5T640,560v-160q0,-17 11.5,-28.5T680,360h120q17,0 28.5,11.5T840,400v40h-60v-20h-80v120h80v-20h60v40q0,17 -11.5,28.5T800,600L680,600ZM380,600v-240h160q17,0 28.5,11.5T580,400v40q0,17 -11.5,28.5T540,480q17,0 28.5,11.5T580,520v40q0,17 -11.5,28.5T540,600L380,600ZM440,450h80v-30h-80v30ZM440,540h80v-30h-80v30ZM120,600v-200q0,-17 11.5,-28.5T160,360h120q17,0 28.5,11.5T320,400v200h-60v-60h-80v60h-60ZM180,480h80v-60h-80v60Z"
android:fillColor="#000000"/>
</vector>

View 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="m424,648 l282,-282 -56,-56 -226,226 -114,-114 -56,56 170,170ZM200,840q-33,0 -56.5,-23.5T120,760v-560q0,-33 23.5,-56.5T200,120h560q33,0 56.5,23.5T840,200v560q0,33 -23.5,56.5T760,840L200,840ZM200,760h560v-560L200,200v560ZM200,200v560,-560Z"
android:fillColor="#000000"/>
</vector>

View 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="M220,600v-180h-60v-60h120v240h-60ZM360,600v-100q0,-17 11.5,-28.5T400,460h80v-40L360,420v-60h140q17,0 28.5,11.5T540,400v60q0,17 -11.5,28.5T500,500h-80v40h120v60L360,600ZM600,600v-60h120v-40h-80v-40h80v-40L600,420v-60h140q17,0 28.5,11.5T780,400v160q0,17 -11.5,28.5T740,600L600,600Z"
android:fillColor="#000000"/>
</vector>

View 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="M480,680q83,0 141.5,-58.5T680,480q0,-83 -58.5,-141.5T480,280q-83,0 -141.5,58.5T280,480q0,83 58.5,141.5T480,680ZM480,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,93Z"
android:fillColor="#000000"/>
</vector>

View 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="M280,720q-100,0 -170,-70T40,480q0,-100 70,-170t170,-70h400q100,0 170,70t70,170q0,100 -70,170t-170,70L280,720ZM280,640h400q66,0 113,-47t47,-113q0,-66 -47,-113t-113,-47L280,320q-66,0 -113,47t-47,113q0,66 47,113t113,47ZM280,600q50,0 85,-35t35,-85q0,-50 -35,-85t-85,-35q-50,0 -85,35t-35,85q0,50 35,85t85,35ZM480,480Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -374,6 +374,9 @@
<string name="edit_app_group">编辑组</string> <string name="edit_app_group">编辑组</string>
<string name="add_to_list">添加到列表</string> <string name="add_to_list">添加到列表</string>
<string name="remove_from_list">从列表中移除</string> <string name="remove_from_list">从列表中移除</string>
<string name="managed_configuration">托管配置</string>
<string name="clear_configurations">清除配置</string>
<string name="specify_value">指定值</string>
<!--UserRestriction--> <!--UserRestriction-->
<string name="user_restriction">用户限制</string> <string name="user_restriction">用户限制</string>

View File

@@ -408,6 +408,9 @@
<string name="edit_app_group">Edit group</string> <string name="edit_app_group">Edit group</string>
<string name="add_to_list">Add to list</string> <string name="add_to_list">Add to list</string>
<string name="remove_from_list">Remove from list</string> <string name="remove_from_list">Remove from list</string>
<string name="managed_configuration">Managed configuration</string>
<string name="clear_configurations">Clear configurations</string>
<string name="specify_value">Specify value</string>
<!--UserRestriction--> <!--UserRestriction-->
<string name="user_restriction">User restriction</string> <string name="user_restriction">User restriction</string>