mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 11:05:59 +00:00
Items ordering in managed configurations
Check FingerprintManager exist before startBiometricsUnlock(), fix #200, #201, #202
This commit is contained in:
@@ -103,6 +103,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.fragment)
|
implementation(libs.androidx.fragment)
|
||||||
implementation(libs.hiddenApiBypass)
|
implementation(libs.hiddenApiBypass)
|
||||||
implementation(libs.libsu)
|
implementation(libs.libsu)
|
||||||
|
implementation(libs.reoderable)
|
||||||
implementation(libs.serialization)
|
implementation(libs.serialization)
|
||||||
implementation(kotlin("reflect"))
|
implementation(kotlin("reflect"))
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.bintianqi.owndroid
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.hardware.biometrics.BiometricPrompt
|
import android.hardware.biometrics.BiometricPrompt
|
||||||
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
|
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
|
||||||
|
import android.hardware.fingerprint.FingerprintManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.CancellationSignal
|
import android.os.CancellationSignal
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
@@ -92,6 +93,7 @@ fun AppLockDialog(onSucceed: () -> Unit, onDismiss: () -> Unit) = Dialog(onDismi
|
|||||||
|
|
||||||
@RequiresApi(28)
|
@RequiresApi(28)
|
||||||
fun startBiometricsUnlock(context: Context, onSucceed: () -> Unit) {
|
fun startBiometricsUnlock(context: Context, onSucceed: () -> Unit) {
|
||||||
|
context.getSystemService(FingerprintManager::class.java) ?: return
|
||||||
val callback = object : AuthenticationCallback() {
|
val callback = object : AuthenticationCallback() {
|
||||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {
|
||||||
super.onAuthenticationSucceeded(result)
|
super.onAuthenticationSucceeded(result)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ 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
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
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
|
||||||
@@ -118,6 +119,8 @@ import kotlinx.coroutines.channels.Channel
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import sh.calvin.reorderable.ReorderableItem
|
||||||
|
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||||
|
|
||||||
val String.isValidPackageName
|
val String.isValidPackageName
|
||||||
get() = Regex("""^(?:[a-zA-Z]\w*\.)+[a-zA-Z]\w*$""").matches(this)
|
get() = Regex("""^(?:[a-zA-Z]\w*\.)+[a-zA-Z]\w*$""").matches(this)
|
||||||
@@ -265,7 +268,7 @@ fun ApplicationDetailsScreen(
|
|||||||
val appRestrictions by vm.appRestrictions.collectAsStateWithLifecycle()
|
val appRestrictions by vm.appRestrictions.collectAsStateWithLifecycle()
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
vm.getAppStatus(packageName)
|
vm.getAppStatus(packageName)
|
||||||
vm.getAppRestrictions(packageName)
|
if (VERSION.SDK_INT >= 23) vm.getAppRestrictions(packageName)
|
||||||
}
|
}
|
||||||
MySmallTitleScaffold(R.string.place_holder, onNavigateUp, 0.dp) {
|
MySmallTitleScaffold(R.string.place_holder, onNavigateUp, 0.dp) {
|
||||||
Column(Modifier.align(Alignment.CenterHorizontally).padding(top = 16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(Modifier.align(Alignment.CenterHorizontally).padding(top = 16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
@@ -1102,13 +1105,7 @@ fun ManagedConfigurationScreen(
|
|||||||
is AppRestriction.StringItem -> entry.value?.take(30)
|
is AppRestriction.StringItem -> entry.value?.take(30)
|
||||||
is AppRestriction.BooleanItem -> entry.value?.toString()
|
is AppRestriction.BooleanItem -> entry.value?.toString()
|
||||||
is AppRestriction.ChoiceItem -> entry.value
|
is AppRestriction.ChoiceItem -> entry.value
|
||||||
is AppRestriction.MultiSelectItem -> {
|
is AppRestriction.MultiSelectItem -> entry.value?.joinToString(limit = 30)
|
||||||
if (entry.value != null) {
|
|
||||||
entry.entryValues
|
|
||||||
.filter { entry.value?.contains(it) ?: false }
|
|
||||||
.joinToString(limit = 30)
|
|
||||||
} else null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text ?: "null", Modifier.alpha(0.7F),
|
text ?: "null", Modifier.alpha(0.7F),
|
||||||
@@ -1131,7 +1128,6 @@ fun ManagedConfigurationScreen(
|
|||||||
shape = AlertDialogDefaults.shape,
|
shape = AlertDialogDefaults.shape,
|
||||||
tonalElevation = AlertDialogDefaults.TonalElevation,
|
tonalElevation = AlertDialogDefaults.TonalElevation,
|
||||||
) {
|
) {
|
||||||
Column(Modifier.verticalScroll(rememberScrollState()).padding(12.dp)) {
|
|
||||||
ManagedConfigurationDialog(dialog!!) {
|
ManagedConfigurationDialog(dialog!!) {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
setRestriction(params.packageName, it)
|
setRestriction(params.packageName, it)
|
||||||
@@ -1140,7 +1136,6 @@ fun ManagedConfigurationScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (clearRestrictionDialog) AlertDialog(
|
if (clearRestrictionDialog) AlertDialog(
|
||||||
text = {
|
text = {
|
||||||
Text(stringResource(R.string.clear_configurations))
|
Text(stringResource(R.string.clear_configurations))
|
||||||
@@ -1167,13 +1162,24 @@ fun ManagedConfigurationScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ColumnScope.ManagedConfigurationDialog(
|
fun ManagedConfigurationDialog(
|
||||||
restriction: AppRestriction, setRestriction: (AppRestriction?) -> Unit
|
restriction: AppRestriction, setRestriction: (AppRestriction?) -> Unit
|
||||||
) {
|
) {
|
||||||
var specifyValue by remember { mutableStateOf(false) }
|
var specifyValue by remember { mutableStateOf(false) }
|
||||||
var input by remember { mutableStateOf("") }
|
var input by remember { mutableStateOf("") }
|
||||||
var inputState by remember { mutableStateOf(false) }
|
var inputState by remember { mutableStateOf(false) }
|
||||||
val inputSelections = remember { mutableStateListOf<String>() }
|
val multiSelectList = remember {
|
||||||
|
mutableStateListOf(
|
||||||
|
*(if (restriction is AppRestriction.MultiSelectItem) {
|
||||||
|
restriction.entryValues.mapIndexed { index, value ->
|
||||||
|
MultiSelectEntry(
|
||||||
|
value, restriction.entries.getOrNull(index),
|
||||||
|
restriction.value?.contains(value) ?: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else emptyList()).toTypedArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
when (restriction) {
|
when (restriction) {
|
||||||
is AppRestriction.IntItem -> restriction.value?.let {
|
is AppRestriction.IntItem -> restriction.value?.let {
|
||||||
@@ -1193,11 +1199,17 @@ fun ColumnScope.ManagedConfigurationDialog(
|
|||||||
specifyValue = true
|
specifyValue = true
|
||||||
}
|
}
|
||||||
is AppRestriction.MultiSelectItem -> restriction.value?.let {
|
is AppRestriction.MultiSelectItem -> restriction.value?.let {
|
||||||
inputSelections.addAll(it)
|
|
||||||
specifyValue = true
|
specifyValue = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val reorderableListState = rememberReorderableLazyListState(listState) { from, to ->
|
||||||
|
// `-1` because there's an `item` before items
|
||||||
|
multiSelectList.add(from.index - 1, multiSelectList.removeAt(to.index - 1))
|
||||||
|
}
|
||||||
|
LazyColumn(Modifier.padding(12.dp), listState) {
|
||||||
|
item {
|
||||||
SelectionContainer {
|
SelectionContainer {
|
||||||
Column {
|
Column {
|
||||||
restriction.title?.let {
|
restriction.title?.let {
|
||||||
@@ -1218,23 +1230,23 @@ fun ColumnScope.ManagedConfigurationDialog(
|
|||||||
Text(stringResource(R.string.specify_value))
|
Text(stringResource(R.string.specify_value))
|
||||||
Switch(specifyValue, { specifyValue = it })
|
Switch(specifyValue, { specifyValue = it })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (specifyValue) when (restriction) {
|
if (specifyValue) when (restriction) {
|
||||||
is AppRestriction.IntItem -> {
|
is AppRestriction.IntItem -> item {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
input, { input = it }, Modifier.fillMaxWidth(),
|
input, { input = it }, Modifier.fillMaxWidth(),
|
||||||
isError = input.toIntOrNull() == null
|
isError = input.toIntOrNull() == null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is AppRestriction.StringItem -> {
|
is AppRestriction.StringItem -> item {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
input, { input = it }, Modifier.fillMaxWidth()
|
input, { input = it }, Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is AppRestriction.BooleanItem -> {
|
is AppRestriction.BooleanItem -> item {
|
||||||
Switch(inputState, { inputState = it })
|
Switch(inputState, { inputState = it })
|
||||||
}
|
}
|
||||||
is AppRestriction.ChoiceItem -> {
|
is AppRestriction.ChoiceItem -> itemsIndexed(restriction.entryValues) { index, value ->
|
||||||
restriction.entryValues.forEachIndexed { index, value ->
|
|
||||||
val label = restriction.entries.getOrNull(index)
|
val label = restriction.entries.getOrNull(index)
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth().clickable {
|
Modifier.fillMaxWidth().clickable {
|
||||||
@@ -1253,32 +1265,39 @@ fun ColumnScope.ManagedConfigurationDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
is AppRestriction.MultiSelectItem -> itemsIndexed(
|
||||||
is AppRestriction.MultiSelectItem -> {
|
multiSelectList, { _, v -> v.value }
|
||||||
restriction.entryValues.forEachIndexed { index, value ->
|
) { index, entry ->
|
||||||
val label = restriction.entries.getOrNull(index)
|
ReorderableItem(reorderableListState, entry.value) {
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth().clickable {
|
Modifier.fillMaxWidth().clickable {
|
||||||
if (value in inputSelections)
|
val old = multiSelectList[index]
|
||||||
inputSelections -= value else inputSelections += value
|
multiSelectList[index] = old.copy(selected = !old.selected)
|
||||||
}.padding(8.dp, 4.dp),
|
}.padding(8.dp, 4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
Arrangement.SpaceBetween, Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Checkbox(value in inputSelections, null)
|
Row(Modifier.weight(1F), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Checkbox(entry.selected, null)
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
if (label == null) {
|
if (entry.title == null) {
|
||||||
Text(value)
|
Text(entry.value)
|
||||||
} else {
|
} else {
|
||||||
Column {
|
Column {
|
||||||
Text(label)
|
Text(entry.title)
|
||||||
Text(value, Modifier.alpha(0.7F), style = typography.bodyMedium)
|
Text(entry.value, Modifier.alpha(0.7F), style = typography.bodyMedium)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.drag_indicator_fill0), null,
|
||||||
|
Modifier.draggableHandle()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
item {
|
||||||
}
|
Row(Modifier.fillMaxWidth().padding(top = 4.dp), Arrangement.End) {
|
||||||
Row(Modifier.align(Alignment.End).padding(top = 4.dp)) {
|
|
||||||
TextButton({
|
TextButton({
|
||||||
setRestriction(null)
|
setRestriction(null)
|
||||||
}, Modifier.padding(end = 4.dp)) {
|
}, Modifier.padding(end = 4.dp)) {
|
||||||
@@ -1299,7 +1318,10 @@ fun ColumnScope.ManagedConfigurationDialog(
|
|||||||
value = if (specifyValue) input else null
|
value = if (specifyValue) input else null
|
||||||
)
|
)
|
||||||
is AppRestriction.MultiSelectItem -> restriction.copy(
|
is AppRestriction.MultiSelectItem -> restriction.copy(
|
||||||
value = if (specifyValue) inputSelections.toTypedArray() else null
|
value = if (specifyValue)
|
||||||
|
multiSelectList.filter { it.selected }
|
||||||
|
.map { it.value }.toTypedArray()
|
||||||
|
else null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
setRestriction(newRestriction)
|
setRestriction(newRestriction)
|
||||||
@@ -1308,6 +1330,8 @@ fun ColumnScope.ManagedConfigurationDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sealed class AppRestriction(
|
sealed class AppRestriction(
|
||||||
open val key: String, open val title: String?, open val description: String?
|
open val key: String, open val title: String?, open val description: String?
|
||||||
@@ -1347,3 +1371,5 @@ sealed class AppRestriction(
|
|||||||
var value: Array<String>?
|
var value: Array<String>?
|
||||||
) : AppRestriction(key, title, description)
|
) : AppRestriction(key, title, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class MultiSelectEntry(val value: String, val title: String?, val selected: Boolean)
|
||||||
|
|||||||
9
app/src/main/res/drawable/drag_indicator_fill0.xml
Normal file
9
app/src/main/res/drawable/drag_indicator_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="M360,800q-33,0 -56.5,-23.5T280,720q0,-33 23.5,-56.5T360,640q33,0 56.5,23.5T440,720q0,33 -23.5,56.5T360,800ZM600,800q-33,0 -56.5,-23.5T520,720q0,-33 23.5,-56.5T600,640q33,0 56.5,23.5T680,720q0,33 -23.5,56.5T600,800ZM360,560q-33,0 -56.5,-23.5T280,480q0,-33 23.5,-56.5T360,400q33,0 56.5,23.5T440,480q0,33 -23.5,56.5T360,560ZM600,560q-33,0 -56.5,-23.5T520,480q0,-33 23.5,-56.5T600,400q33,0 56.5,23.5T680,480q0,33 -23.5,56.5T600,560ZM360,320q-33,0 -56.5,-23.5T280,240q0,-33 23.5,-56.5T360,160q33,0 56.5,23.5T440,240q0,33 -23.5,56.5T360,320ZM600,320q-33,0 -56.5,-23.5T520,240q0,-33 23.5,-56.5T600,160q33,0 56.5,23.5T680,240q0,33 -23.5,56.5T600,320Z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
||||||
@@ -12,6 +12,7 @@ dhizuku = "2.5.4"
|
|||||||
dhizuku-server = "0.0.10"
|
dhizuku-server = "0.0.10"
|
||||||
hiddenApiBypass = "6.1"
|
hiddenApiBypass = "6.1"
|
||||||
libsu = "6.0.0"
|
libsu = "6.0.0"
|
||||||
|
reoderable = "3.0.0"
|
||||||
serialization = "1.9.0"
|
serialization = "1.9.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
@@ -33,6 +34,7 @@ dhizuku-api = { module = "io.github.iamr0s:Dhizuku-API", version.ref = "dhizuku"
|
|||||||
dhizuku-server-api = { group = "io.github.iamr0s", name = "Dhizuku-SERVER_API", version.ref = "dhizuku-server" }
|
dhizuku-server-api = { group = "io.github.iamr0s", name = "Dhizuku-SERVER_API", version.ref = "dhizuku-server" }
|
||||||
hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" }
|
hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" }
|
||||||
libsu = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
|
libsu = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
|
||||||
|
reoderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reoderable" }
|
||||||
|
|
||||||
serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
|
serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user