Fix crash in PackageChooser

Add 2 new user restriction items, close #124
Add User restriction editor
Add Exit in settings
This commit is contained in:
BinTianqi
2025-05-31 11:59:53 +08:00
parent ef800fd6bd
commit 21ddb5a98d
8 changed files with 209 additions and 44 deletions

View File

@@ -13,6 +13,7 @@
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC"/> <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<uses-sdk tools:overrideLibrary="rikka.shizuku.provider,rikka.shizuku.api,rikka.shizuku.shared,rikka.shizuku.aidl"/> <uses-sdk tools:overrideLibrary="rikka.shizuku.provider,rikka.shizuku.api,rikka.shizuku.shared,rikka.shizuku.aidl"/>
<uses-feature android:name="android.software.device_admin"/>
<application <application
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"

View File

@@ -207,6 +207,8 @@ import com.bintianqi.owndroid.dpm.UserInfoScreen
import com.bintianqi.owndroid.dpm.UserOperation import com.bintianqi.owndroid.dpm.UserOperation
import com.bintianqi.owndroid.dpm.UserOperationScreen import com.bintianqi.owndroid.dpm.UserOperationScreen
import com.bintianqi.owndroid.dpm.UserRestriction import com.bintianqi.owndroid.dpm.UserRestriction
import com.bintianqi.owndroid.dpm.UserRestrictionEditor
import com.bintianqi.owndroid.dpm.UserRestrictionEditorScreen
import com.bintianqi.owndroid.dpm.UserRestrictionOptions import com.bintianqi.owndroid.dpm.UserRestrictionOptions
import com.bintianqi.owndroid.dpm.UserRestrictionOptionsScreen import com.bintianqi.owndroid.dpm.UserRestrictionOptionsScreen
import com.bintianqi.owndroid.dpm.UserRestrictionScreen import com.bintianqi.owndroid.dpm.UserRestrictionScreen
@@ -426,10 +428,13 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
composable<SetDefaultDialer> { SetDefaultDialerScreen(::navigateUp) } composable<SetDefaultDialer> { SetDefaultDialerScreen(::navigateUp) }
composable<UserRestriction> { composable<UserRestriction> {
UserRestrictionScreen(::navigateUp) { title, items -> UserRestrictionScreen(::navigateUp) {
navigate(UserRestrictionOptions(title, items)) navigate(it)
} }
} }
composable<UserRestrictionEditor> {
UserRestrictionEditorScreen(::navigateUp)
}
composable<UserRestrictionOptions>(mapOf(serializableNavTypePair<List<Restriction>>())) { composable<UserRestrictionOptions>(mapOf(serializableNavTypePair<List<Restriction>>())) {
UserRestrictionOptionsScreen(it.toRoute(), ::navigateUp) UserRestrictionOptionsScreen(it.toRoute(), ::navigateUp)
} }

View File

@@ -180,10 +180,8 @@ fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Uni
} }
) { paddingValues -> ) { paddingValues ->
LazyColumn(Modifier.fillMaxSize().padding(paddingValues)) { LazyColumn(Modifier.fillMaxSize().padding(paddingValues)) {
stickyHeader { if (progress < 1F) stickyHeader {
AnimatedVisibility(progress < 1F) { LinearProgressIndicator({ progress }, Modifier.fillMaxWidth())
LinearProgressIndicator(progress = { progress }, modifier = Modifier.fillMaxWidth())
}
} }
items(filteredPackages, { it.name }) { items(filteredPackages, { it.name }) {
Row( Row(

View File

@@ -21,6 +21,7 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
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.filled.Close
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
@@ -67,6 +68,7 @@ import java.security.SecureRandom
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import kotlin.system.exitProcess
@Serializable object Settings @Serializable object Settings
@@ -105,6 +107,11 @@ fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
Icon(painterResource(R.drawable.description_fill0), null) Icon(painterResource(R.drawable.description_fill0), null)
} }
) )
DropdownMenuItem(
{ Text(stringResource(R.string.exit)) },
{ exitProcess(0) },
leadingIcon = { Icon(Icons.Default.Close, null) }
)
} }
} }
} }

View File

@@ -271,7 +271,8 @@ fun WorkModesScreen(
) )
} }
if ((privilege.device || privilege.profile) && !privilege.dhizuku) Row( if ((privilege.device || privilege.profile) && !privilege.dhizuku) Row(
Modifier.padding(top = 20.dp).fillMaxWidth().clickable { onNavigate(DhizukuServerSettings) }, Modifier.padding(top = 20.dp).fillMaxWidth()
.clickable { onNavigate(DhizukuServerSettings) }.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(painterResource(R.drawable.dhizuku_icon), null, Modifier.padding(8.dp).size(28.dp)) Icon(painterResource(R.drawable.dhizuku_icon), null, Modifier.padding(8.dp).size(28.dp))

View File

@@ -6,24 +6,67 @@ import android.os.UserManager
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.annotation.StringRes import androidx.annotation.StringRes
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.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
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.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.HorizontalPadding
import com.bintianqi.owndroid.R import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.myPrivilege import com.bintianqi.owndroid.myPrivilege
import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.MyLazyScaffold
import com.bintianqi.owndroid.ui.MyScaffold import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.NavIcon
import com.bintianqi.owndroid.ui.SwitchItem import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.zhCN
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
@@ -36,35 +79,62 @@ data class Restriction(
@Serializable object UserRestriction @Serializable object UserRestriction
@OptIn(ExperimentalMaterial3Api::class)
@RequiresApi(24) @RequiresApi(24)
@Composable @Composable
fun UserRestrictionScreen(onNavigateUp: () -> Unit, onNavigate: (Int, List<Restriction>) -> Unit) { fun UserRestrictionScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
val privilege by myPrivilege.collectAsStateWithLifecycle() val privilege by myPrivilege.collectAsStateWithLifecycle()
MyScaffold(R.string.user_restriction, onNavigateUp, 0.dp) { val sb = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Spacer(Modifier.padding(vertical = 2.dp)) fun navigateToOptions(title: Int, items: List<Restriction>) {
Text(text = stringResource(R.string.switch_to_disable_feature), modifier = Modifier.padding(start = 16.dp)) onNavigate(UserRestrictionOptions(title, items))
if(privilege.profile) { Text(text = stringResource(R.string.profile_owner_is_restricted), modifier = Modifier.padding(start = 16.dp)) } }
if(privilege.work) { Scaffold(
Text(text = stringResource(R.string.some_features_invalid_in_work_profile), modifier = Modifier.padding(start = 16.dp)) Modifier.nestedScroll(sb.nestedScrollConnection),
topBar = {
LargeTopAppBar(
{ Text(stringResource(R.string.user_restriction)) },
navigationIcon = { NavIcon(onNavigateUp) },
actions = {
IconButton({ onNavigate(UserRestrictionEditor) }) {
Icon(Icons.Default.Edit, null)
}
},
scrollBehavior = sb
)
} }
Spacer(Modifier.padding(vertical = 2.dp)) ) { paddingValues ->
FunctionItem(R.string.network, icon = R.drawable.language_fill0) { Column(
onNavigate(R.string.network, RestrictionData.internet) modifier = Modifier
} .fillMaxSize()
FunctionItem(R.string.connectivity, icon = R.drawable.devices_other_fill0) { .padding(paddingValues)
onNavigate(R.string.connectivity, RestrictionData.connectivity) .verticalScroll(rememberScrollState())
} .padding(bottom = 80.dp)
FunctionItem(R.string.applications, icon = R.drawable.apps_fill0) { ) {
onNavigate(R.string.applications, RestrictionData.applications) Spacer(Modifier.padding(vertical = 2.dp))
} Text(text = stringResource(R.string.switch_to_disable_feature), modifier = Modifier.padding(start = 16.dp))
FunctionItem(R.string.users, icon = R.drawable.account_circle_fill0) { if(privilege.profile) { Text(text = stringResource(R.string.profile_owner_is_restricted), modifier = Modifier.padding(start = 16.dp)) }
onNavigate(R.string.users, RestrictionData.users) if(privilege.work) {
} Text(text = stringResource(R.string.some_features_invalid_in_work_profile), modifier = Modifier.padding(start = 16.dp))
FunctionItem(R.string.media, icon = R.drawable.volume_up_fill0) { }
onNavigate(R.string.media, RestrictionData.media) Spacer(Modifier.padding(vertical = 2.dp))
} FunctionItem(R.string.network, icon = R.drawable.language_fill0) {
FunctionItem(R.string.other, icon = R.drawable.more_horiz_fill0) { navigateToOptions(R.string.network, RestrictionData.internet)
onNavigate(R.string.other, RestrictionData.other) }
FunctionItem(R.string.connectivity, icon = R.drawable.devices_other_fill0) {
navigateToOptions(R.string.connectivity, RestrictionData.connectivity)
}
FunctionItem(R.string.applications, icon = R.drawable.apps_fill0) {
navigateToOptions(R.string.applications, RestrictionData.applications)
}
FunctionItem(R.string.users, icon = R.drawable.account_circle_fill0) {
navigateToOptions(R.string.users, RestrictionData.users)
}
FunctionItem(R.string.media, icon = R.drawable.volume_up_fill0) {
navigateToOptions(R.string.media, RestrictionData.media)
}
FunctionItem(R.string.other, icon = R.drawable.more_horiz_fill0) {
navigateToOptions(R.string.other, RestrictionData.other)
}
} }
} }
} }
@@ -89,19 +159,37 @@ fun UserRestrictionOptionsScreen(
status.put(it.id, restrictions.getBoolean(it.id)) status.put(it.id, restrictions.getBoolean(it.id))
} }
} }
MyScaffold(data.title, onNavigateUp, 0.dp) { MyLazyScaffold(data.title, onNavigateUp) {
data.items.filter { Build.VERSION.SDK_INT >= it.requiresApi }.forEach { restriction -> items(data.items.filter { Build.VERSION.SDK_INT >= it.requiresApi }) { restriction ->
SwitchItem( Row(
restriction.name, restriction.id, restriction.icon, status[restriction.id] == true, Modifier.fillMaxWidth().padding(15.dp, 6.dp),
{ Arrangement.SpaceBetween, Alignment.CenterVertically
if (it) { ) {
dpm.addUserRestriction(receiver, restriction.id) Row(Modifier.fillMaxWidth(0.8F), verticalAlignment = Alignment.CenterVertically) {
} else { Icon(painterResource(restriction.icon), null, Modifier.padding(start = 6.dp, end = 16.dp))
dpm.clearUserRestriction(receiver, restriction.id) Column {
Text(stringResource(restriction.name), style = typography.titleMedium)
Text(
restriction.id, style = typography.bodyMedium,
color = colorScheme.onBackground.copy(alpha = 0.8F)
)
} }
status[restriction.id] = dpm.getUserRestrictions(receiver).getBoolean(restriction.id) }
}, padding = true Switch(
) status[restriction.id] == true,
{
if (it) {
dpm.addUserRestriction(receiver, restriction.id)
} else {
dpm.clearUserRestriction(receiver, restriction.id)
}
status[restriction.id] = dpm.getUserRestrictions(receiver).getBoolean(restriction.id)
}
)
}
}
item {
Spacer(Modifier.padding(vertical = 30.dp))
} }
} }
} }
@@ -161,6 +249,8 @@ object RestrictionData {
Restriction(UserManager.DISALLOW_ADD_USER, R.string.add_user, R.drawable.account_circle_fill0), Restriction(UserManager.DISALLOW_ADD_USER, R.string.add_user, R.drawable.account_circle_fill0),
Restriction(UserManager.DISALLOW_REMOVE_USER, R.string.remove_user, R.drawable.account_circle_fill0), Restriction(UserManager.DISALLOW_REMOVE_USER, R.string.remove_user, R.drawable.account_circle_fill0),
Restriction(UserManager.DISALLOW_USER_SWITCH, R.string.switch_user, R.drawable.account_circle_fill0, 28), Restriction(UserManager.DISALLOW_USER_SWITCH, R.string.switch_user, R.drawable.account_circle_fill0, 28),
Restriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, R.string.create_work_profile, R.drawable.work_fill0, 26),
Restriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, R.string.delete_work_profile, R.drawable.delete_forever_fill0, 26),
Restriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, R.string.create_private_space, R.drawable.lock_fill0, 35), Restriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, R.string.create_private_space, R.drawable.lock_fill0, 35),
Restriction(UserManager.DISALLOW_SET_USER_ICON, R.string.set_user_icon, R.drawable.account_circle_fill0, 24), Restriction(UserManager.DISALLOW_SET_USER_ICON, R.string.set_user_icon, R.drawable.account_circle_fill0, 24),
Restriction(UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, R.string.cross_profile_copy, R.drawable.content_paste_fill0), Restriction(UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, R.string.cross_profile_copy, R.drawable.content_paste_fill0),
@@ -187,3 +277,64 @@ object RestrictionData {
) )
fun getAllRestrictions() = internet + connectivity + media + applications + users + other fun getAllRestrictions() = internet + connectivity + media + applications + users + other
} }
@Serializable object UserRestrictionEditor
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun UserRestrictionEditorScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val list = remember { mutableStateListOf<String>() }
fun refresh() {
val restrictions = dpm.getUserRestrictions(receiver)
list.clear()
list.addAll(restrictions.keySet().filter { restrictions.getBoolean(it) })
}
LaunchedEffect(Unit) { refresh() }
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.edit)) },
navigationIcon = { NavIcon(onNavigateUp) }
)
}
) { paddingValues ->
LazyColumn(Modifier.fillMaxSize().padding(paddingValues)) {
items(list, { it }) {
Row(
Modifier.fillMaxWidth().padding(HorizontalPadding, 2.dp).animateItem(),
Arrangement.SpaceBetween, Alignment.CenterVertically
) {
Text(it)
IconButton({
dpm.clearUserRestriction(receiver, it)
refresh()
}) {
Icon(Icons.Outlined.Delete, null)
}
}
}
item {
var input by remember { mutableStateOf("") }
fun add() {
dpm.addUserRestriction(receiver, input)
refresh()
input = ""
}
OutlinedTextField(
input, { input = it }, Modifier.fillMaxWidth().padding(HorizontalPadding, 20.dp),
label = { Text("id") },
trailingIcon = {
IconButton(::add, enabled = input.isNotBlank()) {
Icon(Icons.Default.Add, null)
}
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { add() }
)
}
}
}
}

View File

@@ -68,6 +68,7 @@
<string name="default_str">默认</string> <string name="default_str">默认</string>
<string name="timeout">超时</string> <string name="timeout">超时</string>
<string name="continue_str">继续</string> <string name="continue_str">继续</string>
<string name="exit">退出</string>
<!--Permissions--> <!--Permissions-->
<string name="profile_owner">Profile owner</string> <string name="profile_owner">Profile owner</string>

View File

@@ -73,6 +73,7 @@
<string name="default_str">Default</string> <string name="default_str">Default</string>
<string name="timeout">Timeout</string> <string name="timeout">Timeout</string>
<string name="continue_str">Continue</string> <string name="continue_str">Continue</string>
<string name="exit">Exit</string>
<!--Permissions--> <!--Permissions-->
<string name="profile_owner">Profile owner</string> <string name="profile_owner">Profile owner</string>