manager: Add selinux rules UI
This commit is contained in:
@@ -110,4 +110,5 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.sheet.compose.dialogs.core)
|
implementation(libs.sheet.compose.dialogs.core)
|
||||||
implementation(libs.sheet.compose.dialogs.list)
|
implementation(libs.sheet.compose.dialogs.list)
|
||||||
|
implementation(libs.sheet.compose.dialogs.input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,11 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
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.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||||
import androidx.compose.material.icons.filled.ArrowDropUp
|
import androidx.compose.material.icons.filled.ArrowDropUp
|
||||||
import androidx.compose.material.icons.filled.Group
|
|
||||||
import androidx.compose.material3.AssistChip
|
import androidx.compose.material3.AssistChip
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
@@ -25,29 +23,29 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.OutlinedCard
|
import androidx.compose.material3.OutlinedCard
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.platform.testTag
|
|
||||||
import androidx.compose.ui.res.dimensionResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
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.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.text.isDigitsOnly
|
import androidx.core.text.isDigitsOnly
|
||||||
import com.maxkeppeker.sheets.core.models.base.Header
|
import com.maxkeppeker.sheets.core.models.base.Header
|
||||||
import com.maxkeppeker.sheets.core.models.base.IconSource
|
|
||||||
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
|
||||||
import com.maxkeppeker.sheets.core.utils.TestTags
|
import com.maxkeppeler.sheets.input.InputDialog
|
||||||
import com.maxkeppeker.sheets.core.views.IconComponent
|
import com.maxkeppeler.sheets.input.models.InputHeader
|
||||||
|
import com.maxkeppeler.sheets.input.models.InputSelection
|
||||||
|
import com.maxkeppeler.sheets.input.models.InputTextField
|
||||||
|
import com.maxkeppeler.sheets.input.models.InputTextFieldType
|
||||||
|
import com.maxkeppeler.sheets.input.models.ValidationResult
|
||||||
import com.maxkeppeler.sheets.list.ListDialog
|
import com.maxkeppeler.sheets.list.ListDialog
|
||||||
import com.maxkeppeler.sheets.list.models.ListOption
|
import com.maxkeppeler.sheets.list.models.ListOption
|
||||||
import com.maxkeppeler.sheets.list.models.ListSelection
|
import com.maxkeppeler.sheets.list.models.ListSelection
|
||||||
@@ -55,8 +53,9 @@ import me.weishu.kernelsu.Natives
|
|||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.profile.Capabilities
|
import me.weishu.kernelsu.profile.Capabilities
|
||||||
import me.weishu.kernelsu.profile.Groups
|
import me.weishu.kernelsu.profile.Groups
|
||||||
|
import me.weishu.kernelsu.ui.util.isSepolicyValid
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RootProfileConfig(
|
fun RootProfileConfig(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@@ -172,26 +171,15 @@ fun RootProfileConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ListItem(headlineContent = {
|
SELinuxPanel(profile = profile, onSELinuxChange = { domain, rules ->
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
onProfileChange(
|
||||||
OutlinedTextField(
|
profile.copy(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
context = domain,
|
||||||
label = { Text(text = stringResource(R.string.profile_selinux_context)) },
|
rootUseDefault = false
|
||||||
value = profile.context,
|
)
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Ascii,
|
|
||||||
imeAction = ImeAction.Done,
|
|
||||||
),
|
|
||||||
keyboardActions = KeyboardActions(onDone = {
|
|
||||||
keyboardController?.hide()
|
|
||||||
}),
|
|
||||||
onValueChange = {
|
|
||||||
onProfileChange(profile.copy(context = it, rootUseDefault = false))
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,6 +356,89 @@ private fun UidPanel(uid: Int, label: String, onUidChange: (Int) -> Unit) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SELinuxPanel(profile: Natives.Profile, onSELinuxChange: (domain: String, rules: String) -> Unit) {
|
||||||
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
if (showDialog) {
|
||||||
|
var domain by remember { mutableStateOf(profile.context) }
|
||||||
|
var rules by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val inputOptions = listOf(
|
||||||
|
InputTextField(
|
||||||
|
text = domain,
|
||||||
|
header = InputHeader(
|
||||||
|
title = stringResource(id = R.string.profile_selinux_domain),
|
||||||
|
),
|
||||||
|
type = InputTextFieldType.OUTLINED,
|
||||||
|
required = true,
|
||||||
|
resultListener = {
|
||||||
|
domain = it ?: ""
|
||||||
|
},
|
||||||
|
validationListener = { value ->
|
||||||
|
// value can be a-zA-Z0-9_
|
||||||
|
val regex = Regex("^[a-z_]+:[a-z0-9_]+:[a-z0-9_]+(:[a-z0-9_]+)?$")
|
||||||
|
if (value?.matches(regex) == true) ValidationResult.Valid
|
||||||
|
else ValidationResult.Invalid("Domain must be valid sepolicy")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
InputTextField(
|
||||||
|
text = rules,
|
||||||
|
header = InputHeader(
|
||||||
|
title = stringResource(id = R.string.profile_selinux_rules),
|
||||||
|
),
|
||||||
|
type = InputTextFieldType.OUTLINED,
|
||||||
|
singleLine = false,
|
||||||
|
resultListener = {
|
||||||
|
rules = it ?: ""
|
||||||
|
},
|
||||||
|
validationListener = { value ->
|
||||||
|
if (isSepolicyValid(value)) ValidationResult.Valid
|
||||||
|
else ValidationResult.Invalid("Rules must be valid sepolicy")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
InputDialog(
|
||||||
|
state = rememberUseCaseState(visible = true,
|
||||||
|
onFinishedRequest = {
|
||||||
|
onSELinuxChange(domain, rules)
|
||||||
|
},
|
||||||
|
onCloseRequest = {
|
||||||
|
showDialog = false
|
||||||
|
}),
|
||||||
|
header = Header.Default(
|
||||||
|
title = stringResource(R.string.profile_selinux_context),
|
||||||
|
),
|
||||||
|
selection = InputSelection(
|
||||||
|
input = inputOptions,
|
||||||
|
onPositiveClick = { result ->
|
||||||
|
// Handle selection
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ListItem(headlineContent = {
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
showDialog = true
|
||||||
|
},
|
||||||
|
enabled = false,
|
||||||
|
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||||
|
disabledTextColor = MaterialTheme.colorScheme.onSurface,
|
||||||
|
disabledBorderColor = MaterialTheme.colorScheme.outline,
|
||||||
|
disabledPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
),
|
||||||
|
label = { Text(text = stringResource(R.string.profile_selinux_context)) },
|
||||||
|
value = profile.context,
|
||||||
|
onValueChange = { },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun RootProfileConfigPreview() {
|
private fun RootProfileConfigPreview() {
|
||||||
|
|||||||
@@ -140,4 +140,14 @@ fun hasMagisk(): Boolean {
|
|||||||
val result = shell.newJob().add("nsenter --mount=/proc/1/ns/mnt which magisk").exec()
|
val result = shell.newJob().add("nsenter --mount=/proc/1/ns/mnt which magisk").exec()
|
||||||
Log.i(TAG, "has magisk: ${result.isSuccess}")
|
Log.i(TAG, "has magisk: ${result.isSuccess}")
|
||||||
return result.isSuccess
|
return result.isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSepolicyValid(rules: String?): Boolean {
|
||||||
|
if (rules == null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
val shell = getRootShell()
|
||||||
|
val result =
|
||||||
|
shell.newJob().add("ksud sepolicy check '$rules'").to(ArrayList(), null).exec()
|
||||||
|
return result.isSuccess
|
||||||
}
|
}
|
||||||
@@ -74,4 +74,6 @@
|
|||||||
<string name="settings_umount_modules_default">Umount modules by default</string>
|
<string name="settings_umount_modules_default">Umount modules by default</string>
|
||||||
<string name="settings_umount_modules_default_summary">The global default value for \"Umount modules\" in App Profiles. If enabled, it will remove all module modifications to the system for applications that do not have a Profile set.</string>
|
<string name="settings_umount_modules_default_summary">The global default value for \"Umount modules\" in App Profiles. If enabled, it will remove all module modifications to the system for applications that do not have a Profile set.</string>
|
||||||
<string name="profile_umount_modules_summary">Enabling this option will allow KernelSU to restore any modified files by the modules for this application.</string>
|
<string name="profile_umount_modules_summary">Enabling this option will allow KernelSU to restore any modified files by the modules for this application.</string>
|
||||||
|
<string name="profile_selinux_domain">Domain</string>
|
||||||
|
<string name="profile_selinux_rules">Rules</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ accompanist = "0.30.0"
|
|||||||
navigation = "2.5.3"
|
navigation = "2.5.3"
|
||||||
compose-destination = "1.9.42-beta"
|
compose-destination = "1.9.42-beta"
|
||||||
libsu = "5.0.5"
|
libsu = "5.0.5"
|
||||||
sheets-compose-dialogs = "1.1.1"
|
sheets-compose-dialogs = "1.2.0"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
agp-app = { id = "com.android.application", version.ref = "agp" }
|
agp-app = { id = "com.android.application", version.ref = "agp" }
|
||||||
@@ -55,3 +55,4 @@ compose-destinations-ksp = { group = "io.github.raamcosta.compose-destinations",
|
|||||||
|
|
||||||
sheet-compose-dialogs-core = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "core", version.ref = "sheets-compose-dialogs"}
|
sheet-compose-dialogs-core = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "core", version.ref = "sheets-compose-dialogs"}
|
||||||
sheet-compose-dialogs-list = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "list", version.ref = "sheets-compose-dialogs"}
|
sheet-compose-dialogs-list = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "list", version.ref = "sheets-compose-dialogs"}
|
||||||
|
sheet-compose-dialogs-input = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "input", version.ref = "sheets-compose-dialogs"}
|
||||||
|
|||||||
Reference in New Issue
Block a user