mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 19:15:58 +00:00
Implement Managed configurations (#198)
This commit is contained in:
@@ -137,6 +137,8 @@ import com.bintianqi.owndroid.dpm.LockTaskMode
|
||||
import com.bintianqi.owndroid.dpm.LockTaskModeScreen
|
||||
import com.bintianqi.owndroid.dpm.ManageAppGroups
|
||||
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.MtePolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.NearbyStreamingPolicy
|
||||
@@ -598,6 +600,12 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
composable<SetDefaultDialer> {
|
||||
SetDefaultDialerScreen(vm.chosenPackage, ::choosePackage, vm::setDefaultDialer, ::navigateUp)
|
||||
}
|
||||
composable<ManagedConfiguration> {
|
||||
ManagedConfigurationScreen(
|
||||
it.toRoute(), vm.appRestrictions, vm::getAppRestrictions, vm::setAppRestrictions,
|
||||
vm::clearAppRestrictions, ::navigateUp
|
||||
)
|
||||
}
|
||||
composable<ManageAppGroups> {
|
||||
ManageAppGroupsScreen(
|
||||
vm.appGroups,
|
||||
|
||||
@@ -25,6 +25,8 @@ import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.RestrictionEntry
|
||||
import android.content.RestrictionsManager
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageManager
|
||||
@@ -39,6 +41,7 @@ import android.net.wifi.WifiManager
|
||||
import android.net.wifi.WifiSsid
|
||||
import android.os.Binder
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Bundle
|
||||
import android.os.HardwarePropertiesManager
|
||||
import android.os.UserHandle
|
||||
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.ApnProtocol
|
||||
import com.bintianqi.owndroid.dpm.AppGroup
|
||||
import com.bintianqi.owndroid.dpm.AppRestriction
|
||||
import com.bintianqi.owndroid.dpm.AppStatus
|
||||
import com.bintianqi.owndroid.dpm.CaCertInfo
|
||||
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>())
|
||||
init {
|
||||
getAppGroups()
|
||||
|
||||
@@ -232,7 +232,7 @@ fun AppLockSettingsScreen(
|
||||
config: AppLockConfig, setConfig: (AppLockConfig) -> Unit,
|
||||
onNavigateUp: () -> Unit
|
||||
) = MyScaffold(R.string.app_lock, onNavigateUp) {
|
||||
var password by rememberSaveable { mutableStateOf(config.password ?: "") }
|
||||
var password by rememberSaveable { mutableStateOf("") }
|
||||
var confirmPassword by rememberSaveable { mutableStateOf("") }
|
||||
var allowBiometrics by rememberSaveable { mutableStateOf(config.biometrics) }
|
||||
var lockWhenLeaving by rememberSaveable { mutableStateOf(config.whenLeaving) }
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
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.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.Delete
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.AlertDialogDefaults
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
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.typography
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
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.res.painterResource
|
||||
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.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.AppInfo
|
||||
@@ -285,6 +295,11 @@ fun ApplicationDetailsScreen(
|
||||
state = status.keepUninstalled,
|
||||
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 }
|
||||
FunctionItem(R.string.uninstall, icon = R.drawable.delete_fill0) { dialog = 2 }
|
||||
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)
|
||||
}
|
||||
|
||||
9
app/src/main/res/drawable/abc_fill0.xml
Normal file
9
app/src/main/res/drawable/abc_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="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>
|
||||
9
app/src/main/res/drawable/check_box_fill0.xml
Normal file
9
app/src/main/res/drawable/check_box_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="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>
|
||||
9
app/src/main/res/drawable/number_123_fill0.xml
Normal file
9
app/src/main/res/drawable/number_123_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="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>
|
||||
9
app/src/main/res/drawable/radio_button_checked_fill0.xml
Normal file
9
app/src/main/res/drawable/radio_button_checked_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="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>
|
||||
9
app/src/main/res/drawable/toggle_off_fill0.xml
Normal file
9
app/src/main/res/drawable/toggle_off_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="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>
|
||||
@@ -374,6 +374,9 @@
|
||||
<string name="edit_app_group">编辑组</string>
|
||||
<string name="add_to_list">添加到列表</string>
|
||||
<string name="remove_from_list">从列表中移除</string>
|
||||
<string name="managed_configuration">托管配置</string>
|
||||
<string name="clear_configurations">清除配置</string>
|
||||
<string name="specify_value">指定值</string>
|
||||
|
||||
<!--UserRestriction-->
|
||||
<string name="user_restriction">用户限制</string>
|
||||
|
||||
@@ -408,6 +408,9 @@
|
||||
<string name="edit_app_group">Edit group</string>
|
||||
<string name="add_to_list">Add to 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-->
|
||||
<string name="user_restriction">User restriction</string>
|
||||
|
||||
Reference in New Issue
Block a user