From 3d6e12581bc3a642b37b6608041404318f6fca08 Mon Sep 17 00:00:00 2001 From: BinTianqi Date: Sun, 16 Feb 2025 16:13:44 +0800 Subject: [PATCH] Add delegated admin in a new screen Some UI improvements Info of password complexity --- .../com/bintianqi/owndroid/MainActivity.kt | 24 +- .../com/bintianqi/owndroid/dpm/Network.kt | 30 ++- .../com/bintianqi/owndroid/dpm/Password.kt | 66 +++--- .../com/bintianqi/owndroid/dpm/Permissions.kt | 150 ++++++------ .../java/com/bintianqi/owndroid/dpm/System.kt | 218 +++++++++--------- .../java/com/bintianqi/owndroid/dpm/Users.kt | 10 +- .../com/bintianqi/owndroid/ui/Components.kt | 6 +- app/src/main/res/values-ru/strings.xml | 15 +- app/src/main/res/values-tr/strings.xml | 12 +- app/src/main/res/values-zh-rCN/strings.xml | 14 +- app/src/main/res/values/strings.xml | 14 +- 11 files changed, 299 insertions(+), 260 deletions(-) diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index c7d64fb..d9882c3 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -62,6 +62,8 @@ import androidx.navigation.compose.rememberNavController import androidx.navigation.toRoute import com.bintianqi.owndroid.dpm.Accounts import com.bintianqi.owndroid.dpm.AccountsScreen +import com.bintianqi.owndroid.dpm.AddDelegatedAdmin +import com.bintianqi.owndroid.dpm.AddDelegatedAdminScreen import com.bintianqi.owndroid.dpm.AddNetwork import com.bintianqi.owndroid.dpm.AddNetworkScreen import com.bintianqi.owndroid.dpm.AffiliationId @@ -167,7 +169,7 @@ import com.bintianqi.owndroid.dpm.SystemManager import com.bintianqi.owndroid.dpm.SystemManagerScreen import com.bintianqi.owndroid.dpm.SystemOptions import com.bintianqi.owndroid.dpm.SystemOptionsScreen -import com.bintianqi.owndroid.dpm.SystemUpdatePolicy +import com.bintianqi.owndroid.dpm.SystemUpdatePolicyScreen import com.bintianqi.owndroid.dpm.TransferOwnership import com.bintianqi.owndroid.dpm.TransferOwnershipScreen import com.bintianqi.owndroid.dpm.UserInfo @@ -264,6 +266,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { } val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle() fun navigateUp() { navController.navigateUp() } + fun navigate(destination: Any) { navController.navigate(destination) } @Suppress("NewApi") NavHost( navController = navController, startDestination = Home, @@ -290,13 +293,14 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable { DeviceAdminScreen(::navigateUp) } composable { ProfileOwnerScreen(::navigateUp) } composable { DeviceOwnerScreen(::navigateUp) } - composable { DelegatedAdminsScreen(::navigateUp) } + composable { DelegatedAdminsScreen(::navigateUp, ::navigate) } + composable{ AddDelegatedAdminScreen(it.toRoute(), ::navigateUp) } composable { DeviceInfoScreen(::navigateUp) } composable { LockScreenInfoScreen(::navigateUp) } composable { SupportMessageScreen(::navigateUp) } composable { TransferOwnershipScreen(::navigateUp) } - composable { SystemManagerScreen(::navigateUp) { navController.navigate(it) } } + composable { SystemManagerScreen(::navigateUp, ::navigate) } composable { SystemOptionsScreen(::navigateUp) } composable { KeyguardScreen(::navigateUp) } composable { HardwareMonitorScreen(::navigateUp) } @@ -311,12 +315,12 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable { CaCertScreen(::navigateUp) } composable { SecurityLoggingScreen(::navigateUp) } composable { DisableAccountManagementScreen(::navigateUp) } - composable { SystemUpdatePolicy(::navigateUp) } + composable { SystemUpdatePolicyScreen(::navigateUp) } composable { InstallSystemUpdateScreen(::navigateUp) } composable { FrpPolicyScreen(::navigateUp) } composable { WipeDataScreen(::navigateUp) } - composable { NetworkScreen(::navigateUp) { navController.navigate(it) } } + composable { NetworkScreen(::navigateUp, ::navigate) } composable { WifiScreen(::navigateUp, { navController.navigate(it) }) { val dest = navController.graph.findNode(AddNetwork)!!.id @@ -327,7 +331,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable { AddNetworkScreen(it.arguments!!, ::navigateUp) } composable { WifiSecurityLevelScreen(::navigateUp) } composable { WifiSsidPolicyScreen(::navigateUp) } - composable { NetworkStatsScreen(::navigateUp) { navController.navigate(it) } } + composable { NetworkStatsScreen(::navigateUp, ::navigate) } composable(mapOf(serializableNavTypePair>())) { NetworkStatsViewerScreen(it.toRoute()) { navController.navigateUp() } } @@ -339,7 +343,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable { PreferentialNetworkServiceScreen(::navigateUp) } composable { OverrideApnScreen(::navigateUp) } - composable { WorkProfileScreen(::navigateUp) { navController.navigate(it) } } + composable { WorkProfileScreen(::navigateUp, ::navigate) } composable { OrganizationOwnedProfileScreen(::navigateUp) } composable { CreateWorkProfileScreen(::navigateUp) } composable { SuspendPersonalAppScreen(::navigateUp) } @@ -370,7 +374,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { } } - composable { UsersScreen(::navigateUp) { navController.navigate(it) } } + composable { UsersScreen(::navigateUp, ::navigate) } composable { UserInfoScreen(::navigateUp) } composable { UsersOptionsScreen(::navigateUp) } composable { UserOperationScreen(::navigateUp) } @@ -379,7 +383,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable { UserSessionMessageScreen(::navigateUp) } composable { AffiliationIdScreen(::navigateUp) } - composable { PasswordScreen(::navigateUp) { navController.navigate(it) } } + composable { PasswordScreen(::navigateUp, ::navigate) } composable { PasswordInfoScreen(::navigateUp) } composable { ResetPasswordTokenScreen(::navigateUp) } composable { ResetPasswordScreen(::navigateUp) } @@ -387,7 +391,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) { composable { KeyguardDisabledFeaturesScreen(::navigateUp) } composable { RequiredPasswordQualityScreen(::navigateUp) } - composable { SettingsScreen(::navigateUp) { navController.navigate(it) } } + composable { SettingsScreen(::navigateUp, ::navigate) } composable { SettingsOptionsScreen(::navigateUp) } composable { val theme by vm.theme.collectAsStateWithLifecycle() diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt index 9847db3..e330718 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt @@ -121,6 +121,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource @@ -483,6 +485,7 @@ fun AddNetworkScreen(data: Bundle, onNavigateUp: () -> Unit) { @Composable private fun AddNetworkScreen(wifiConfig: WifiConfiguration? = null, onNavigateUp: () -> Unit) { val context = LocalContext.current + val fm = LocalFocusManager.current var resultDialog by remember { mutableStateOf(false) } var createdNetworkId by remember { mutableIntStateOf(-1) } var createNetworkResult by remember { mutableIntStateOf(0) } @@ -614,21 +617,29 @@ private fun AddNetworkScreen(wifiConfig: WifiConfiguration? = null, onNavigateUp } } AnimatedVisibility(visible = useStaticIp, modifier = Modifier.padding(bottom = 8.dp)) { + val gatewayFr = FocusRequester() + val dnsFr = FocusRequester() Column { OutlinedTextField( value = ipAddress, onValueChange = { ipAddress = it }, placeholder = { Text("192.168.1.2/24") }, label = { Text(stringResource(R.string.ip_address)) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions { gatewayFr.requestFocus() }, modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp) ) OutlinedTextField( value = gatewayAddress, onValueChange = { gatewayAddress = it }, placeholder = { Text("192.168.1.1") }, label = { Text(stringResource(R.string.gateway_address)) }, - modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp) + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions { dnsFr.requestFocus() }, + modifier = Modifier.focusRequester(gatewayFr).fillMaxWidth().padding(bottom = 4.dp) ) OutlinedTextField( value = dnsServers, onValueChange = { dnsServers = it }, label = { Text(stringResource(R.string.dns_servers)) }, minLines = 2, - modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp) + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions { fm.clearFocus() }, + modifier = Modifier.focusRequester(dnsFr).fillMaxWidth().padding(bottom = 4.dp) ) } } @@ -648,19 +659,27 @@ private fun AddNetworkScreen(wifiConfig: WifiConfiguration? = null, onNavigateUp } } AnimatedVisibility(visible = useHttpProxy, modifier = Modifier.padding(bottom = 8.dp)) { + val portFr = FocusRequester() + val exclListFr = FocusRequester() Column { OutlinedTextField( value = httpProxyHost, onValueChange = { httpProxyHost = it }, label = { Text(stringResource(R.string.host)) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + keyboardActions = KeyboardActions { portFr.requestFocus() }, modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp) ) OutlinedTextField( value = httpProxyPort, onValueChange = { httpProxyPort = it }, label = { Text(stringResource(R.string.port)) }, - modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp) + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next, keyboardType = KeyboardType.Number), + keyboardActions = KeyboardActions { exclListFr.requestFocus() }, + modifier = Modifier.focusRequester(portFr).fillMaxWidth().padding(bottom = 4.dp) ) OutlinedTextField( value = httpProxyExclList, onValueChange = { httpProxyExclList = it }, label = { Text(stringResource(R.string.excluded_hosts)) }, minLines = 2, placeholder = { Text("example.com\n*.example.com") }, - modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp) + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions { fm.clearFocus() }, + modifier = Modifier.focusRequester(exclListFr).fillMaxWidth().padding(bottom = 4.dp) ) } } @@ -884,6 +903,7 @@ fun NetworkStats.toBucketList(): List { fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkStatsViewer) -> Unit) { val context = LocalContext.current val deviceOwner = context.isDeviceOwner + val fm = LocalFocusManager.current val nsm = context.getSystemService(NetworkStatsManager::class.java) val coroutine = rememberCoroutineScope() var activeTextField by remember { mutableStateOf(NetworkStatsActiveTextField.None) } //0:None, 1:Network type, 2:Start time, 3:End time @@ -999,6 +1019,8 @@ fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkSta label = { Text(stringResource(R.string.subscriber_id)) }, isError = !readOnly && subscriberId.isNullOrBlank(), trailingIcon = { ExpandExposedTextFieldIcon(activeTextField == NetworkStatsActiveTextField.SubscriberId) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions { fm.clearFocus() }, modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable).fillMaxWidth().padding(bottom = 4.dp) ) ExposedDropdownMenu( diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt index 102bf9f..85839e8 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt @@ -2,7 +2,6 @@ package com.bintianqi.owndroid.dpm import android.annotation.SuppressLint import android.app.KeyguardManager -import android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL @@ -29,7 +28,6 @@ import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import android.app.admin.DevicePolicyManager.RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT import android.app.admin.DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY import android.content.Context -import android.content.Intent import android.os.Build.VERSION import android.os.UserManager import android.widget.Toast @@ -67,12 +65,13 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity -import androidx.navigation.NavHostController import com.bintianqi.owndroid.R import com.bintianqi.owndroid.SharedPrefs import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.CardItem import com.bintianqi.owndroid.ui.CheckBoxItem +import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem +import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.InfoCard import com.bintianqi.owndroid.ui.MyScaffold @@ -219,15 +218,17 @@ fun PasswordInfoScreen(onNavigateUp: () -> Unit) { val receiver = context.getReceiver() val deviceOwner = context.isDeviceOwner val profileOwner = context.isProfileOwner + var dialog by remember { mutableIntStateOf(0) } // 0:none, 1:password complexity MyScaffold(R.string.password_info, 8.dp, onNavigateUp) { if(VERSION.SDK_INT >= 29) { - val passwordComplexity = mapOf( - PASSWORD_COMPLEXITY_NONE to R.string.password_complexity_none, - PASSWORD_COMPLEXITY_LOW to R.string.password_complexity_low, - PASSWORD_COMPLEXITY_MEDIUM to R.string.password_complexity_medium, - PASSWORD_COMPLEXITY_HIGH to R.string.password_complexity_high - ) - CardItem(R.string.current_password_complexity, passwordComplexity[dpm.passwordComplexity] ?: R.string.unknown) + val text = when(dpm.passwordComplexity) { + PASSWORD_COMPLEXITY_NONE -> R.string.none + PASSWORD_COMPLEXITY_LOW -> R.string.low + PASSWORD_COMPLEXITY_MEDIUM -> R.string.medium + PASSWORD_COMPLEXITY_HIGH -> R.string.high + else -> R.string.unknown + } + CardItem(R.string.current_password_complexity, text) { dialog = 1 } } if(deviceOwner || profileOwner) { CardItem(R.string.password_sufficient, dpm.isActivePasswordSufficient.yesOrNo) @@ -236,6 +237,15 @@ fun PasswordInfoScreen(onNavigateUp: () -> Unit) { CardItem(R.string.unified_password, dpm.isUsingUnifiedPassword(receiver).yesOrNo) } } + if(dialog != 0) AlertDialog( + text = { Text(stringResource(R.string.info_password_complexity)) }, + confirmButton = { + TextButton({ dialog = 0 }) { + Text(stringResource(R.string.confirm)) + } + }, + onDismissRequest = { dialog = 0 } + ) } @Serializable object ResetPasswordToken @@ -430,34 +440,29 @@ fun RequiredPasswordComplexityScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val dpm = context.getDPM() val passwordComplexity = mapOf( - PASSWORD_COMPLEXITY_NONE to R.string.password_complexity_none, - PASSWORD_COMPLEXITY_LOW to R.string.password_complexity_low, - PASSWORD_COMPLEXITY_MEDIUM to R.string.password_complexity_medium, - PASSWORD_COMPLEXITY_HIGH to R.string.password_complexity_high + PASSWORD_COMPLEXITY_NONE to R.string.none, + PASSWORD_COMPLEXITY_LOW to R.string.low, + PASSWORD_COMPLEXITY_MEDIUM to R.string.medium, + PASSWORD_COMPLEXITY_HIGH to R.string.high ) var selectedItem by remember { mutableIntStateOf(PASSWORD_COMPLEXITY_NONE) } LaunchedEffect(Unit) { selectedItem = dpm.requiredPasswordComplexity } - MyScaffold(R.string.required_password_complexity, 8.dp, onNavigateUp) { + MyScaffold(R.string.required_password_complexity, 0.dp, onNavigateUp) { passwordComplexity.forEach { - RadioButtonItem(it.value, selectedItem == it.key) { selectedItem = it.key } + FullWidthRadioButtonItem(it.value, selectedItem == it.key) { selectedItem = it.key } } Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { dpm.requiredPasswordComplexity = selectedItem + selectedItem = dpm.requiredPasswordComplexity context.showOperationResultToast(true) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp) ) { Text(text = stringResource(R.string.apply)) } - Spacer(Modifier.padding(vertical = 5.dp)) - Button( - onClick = { context.startActivity(Intent(ACTION_SET_NEW_PASSWORD)) }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.require_set_new_password)) - } + InfoCard(R.string.info_password_complexity, 8.dp) } } @@ -494,20 +499,19 @@ fun KeyguardDisabledFeaturesScreen(onNavigateUp: () -> Unit) { } LaunchedEffect(mode) { if(mode != 2) flag = dpm.getKeyguardDisabledFeatures(receiver) } LaunchedEffect(Unit) { refresh() } - MyScaffold(R.string.disable_keyguard_features, 8.dp, onNavigateUp) { - RadioButtonItem(R.string.enable_all, mode == 0) { mode = 0 } - RadioButtonItem(R.string.disable_all, mode == 1) { mode = 1 } - RadioButtonItem(R.string.custom, mode == 2) { mode = 2 } + MyScaffold(R.string.disable_keyguard_features, 0.dp, onNavigateUp) { + FullWidthRadioButtonItem(R.string.enable_all, mode == 0) { mode = 0 } + FullWidthRadioButtonItem(R.string.disable_all, mode == 1) { mode = 1 } + FullWidthRadioButtonItem(R.string.custom, mode == 2) { mode = 2 } AnimatedVisibility(mode == 2) { Column { flagsLiat.forEach { - CheckBoxItem(it.first, flag and it.second == it.second) { checked -> + FullWidthCheckBoxItem(it.first, flag and it.second == it.second) { checked -> flag = if(checked) flag or it.second else flag and (flag xor it.second) } } } } - Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { val disabledFeatures = if(mode == 0) KEYGUARD_DISABLE_FEATURES_NONE else if(mode == 1) KEYGUARD_DISABLE_FEATURES_ALL else flag @@ -515,7 +519,7 @@ fun KeyguardDisabledFeaturesScreen(onNavigateUp: () -> Unit) { refresh() context.showOperationResultToast(true) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp) ) { Text(text = stringResource(R.string.apply)) } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt index 8b41727..6e46d27 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt @@ -20,18 +20,17 @@ import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.layout.Arrangement 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.filled.Add +import androidx.compose.material.icons.outlined.Edit import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -497,7 +496,7 @@ fun DeviceOwnerScreen(onNavigateUp: () -> Unit) { } @Suppress("InlinedApi") -private enum class DelegatedScope(val id: String, @StringRes val string: Int, val requiresApi: Int = 0) { +enum class DelegatedScope(val id: String, @StringRes val string: Int, val requiresApi: Int = 0) { AppRestrictions(DevicePolicyManager.DELEGATION_APP_RESTRICTIONS, R.string.manage_application_restrictions), BlockUninstall(DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL, R.string.block_uninstall), CertInstall(DevicePolicyManager.DELEGATION_CERT_INSTALL, R.string.manage_certificates), @@ -515,13 +514,10 @@ private enum class DelegatedScope(val id: String, @StringRes val string: Int, va @RequiresApi(26) @Composable -fun DelegatedAdminsScreen(onNavigateUp: () -> Unit) { +fun DelegatedAdminsScreen(onNavigateUp: () -> Unit, onNavigate: (AddDelegatedAdmin) -> Unit) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() - var dialog by rememberSaveable { mutableIntStateOf(0) } // 0:None, 1:Edit, 2:Add - var inputPackageName by rememberSaveable { mutableStateOf("") } - var selectedScopes by rememberSaveable { mutableStateOf(listOf()) } val packages = remember { mutableStateMapOf>() } fun refresh() { val list = mutableMapOf>() @@ -542,81 +538,91 @@ fun DelegatedAdminsScreen(onNavigateUp: () -> Unit) { LaunchedEffect(Unit) { refresh() } MyScaffold(R.string.delegated_admins, 0.dp, onNavigateUp) { packages.forEach { (pkg, scopes) -> - Column( - modifier = Modifier - .fillMaxWidth() - .clickable { inputPackageName = pkg; selectedScopes = scopes.map { it.id }; dialog = 1 } - .padding(horizontal = 12.dp, vertical = 8.dp) + Row( + Modifier.fillMaxWidth().padding(vertical = 8.dp).padding(start = 14.dp, end = 8.dp), + Arrangement.SpaceBetween ) { - Text(pkg, style = typography.titleLarge) - Text(scopes.size.toString() + " " + stringResource(R.string.delegated_scope)) + Column { + Text(pkg, style = typography.titleMedium) + Text( + scopes.size.toString() + " " + stringResource(R.string.delegated_scope), + color = colorScheme.onSurfaceVariant, style = typography.bodyMedium + ) + } + IconButton({ onNavigate(AddDelegatedAdmin(pkg, scopes)) }) { + Icon(Icons.Outlined.Edit, stringResource(R.string.edit)) + } } } - if(packages.isEmpty()) - Text( - stringResource(R.string.none), - color = colorScheme.onSurfaceVariant, - modifier = Modifier.align(Alignment.CenterHorizontally).padding(vertical = 4.dp) - ) + if(packages.isEmpty()) Text( + stringResource(R.string.none), + color = colorScheme.onSurfaceVariant, + modifier = Modifier.align(Alignment.CenterHorizontally).padding(vertical = 4.dp) + ) Row( modifier = Modifier .fillMaxWidth() - .clickable { inputPackageName = ""; selectedScopes = emptyList(); dialog = 2 } - .padding(vertical = 10.dp, horizontal = 12.dp), + .clickable { onNavigate(AddDelegatedAdmin()) } + .padding(12.dp), verticalAlignment = Alignment.CenterVertically ) { Icon(Icons.Default.Add, null, modifier = Modifier.padding(end = 12.dp)) - Text(stringResource(R.string.add_delegated_admin), style = typography.titleLarge) + Text(stringResource(R.string.add_delegated_admin), style = typography.titleMedium) } - if(dialog != 0) { - val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result -> - result?.let { inputPackageName = it } + } +} + +@Serializable data class AddDelegatedAdmin(val pkg: String = "", val scopes: List = emptyList()) + +@RequiresApi(26) +@Composable +fun AddDelegatedAdminScreen(data: AddDelegatedAdmin, onNavigateUp: () -> Unit) { + val updateMode = data.pkg.isNotEmpty() + val fm = LocalFocusManager.current + val context = LocalContext.current + var input by remember { mutableStateOf(data.pkg) } + val scopes = remember { mutableStateListOf(*data.scopes.toTypedArray()) } + val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result -> + result?.let { input = it } + } + MyScaffold(if(updateMode) R.string.place_holder else R.string.add_delegated_admin, 0.dp, onNavigateUp, !updateMode) { + OutlinedTextField( + value = input, onValueChange = { input = it }, + label = { Text(stringResource(R.string.package_name)) }, + trailingIcon = { + if(!updateMode) IconButton({ choosePackage.launch(null) }) { + Icon(painterResource(R.drawable.list_fill0), null) + } + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions { fm.clearFocus() }, + readOnly = updateMode, + modifier = Modifier.fillMaxWidth().padding(8.dp) + ) + DelegatedScope.entries.filter { VERSION.SDK_INT >= it.requiresApi }.forEach {scope -> + FullWidthCheckBoxItem(scope.string, scope in scopes) { + if(it) scopes += scope else scopes -= scope } - AlertDialog( - text = { - val fm = LocalFocusManager.current - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - OutlinedTextField( - value = inputPackageName, onValueChange = { inputPackageName = it }, - label = { Text(stringResource(R.string.package_name)) }, - trailingIcon = { - if(dialog == 2) IconButton({ choosePackage.launch(null) }) { - Icon(painterResource(R.drawable.list_fill0), null) - } - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions { fm.clearFocus() }, - readOnly = dialog == 1, - modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp) - ) - DelegatedScope.entries.forEach { scope -> - if(VERSION.SDK_INT >= scope.requiresApi) { - CheckBoxItem(scope.string, scope.id in selectedScopes) { - if(it) selectedScopes += scope.id else selectedScopes -= scope.id - } - } - } - } - }, - confirmButton = { - TextButton( - onClick = { - dpm.setDelegatedScopes(receiver, inputPackageName, selectedScopes) - refresh() - dialog = 0 - }, - enabled = inputPackageName.isNotBlank() - ) { - Text(stringResource(if(dialog == 1) R.string.apply else R.string.add)) - } - }, - dismissButton = { - TextButton({ dialog = 0 }) { - Text(stringResource(R.string.cancel)) - } - }, - onDismissRequest = { dialog = 0 } - ) + } + Button( + onClick = { + context.getDPM().setDelegatedScopes(context.getReceiver(), input, scopes.map { it.id }) + onNavigateUp() + }, + modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp), + enabled = input.isNotBlank() && (!updateMode || scopes.toList() != data.scopes) + ) { + Text(stringResource(if(updateMode) R.string.update else R.string.add)) + } + if(updateMode) Button( + onClick = { + context.getDPM().setDelegatedScopes(context.getReceiver(), input, emptyList()) + onNavigateUp() + }, + modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp), + colors = ButtonDefaults.buttonColors(colorScheme.error, colorScheme.onError) + ) { + Text(stringResource(R.string.delete)) } } } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt index dd4d5e1..225d031 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt @@ -133,6 +133,7 @@ import com.bintianqi.owndroid.humanReadableDate import com.bintianqi.owndroid.parseDate import com.bintianqi.owndroid.showOperationResultToast import com.bintianqi.owndroid.ui.CheckBoxItem +import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem import com.bintianqi.owndroid.ui.FunctionItem import com.bintianqi.owndroid.ui.InfoCard import com.bintianqi.owndroid.ui.ListItem @@ -847,13 +848,13 @@ fun ContentProtectionPolicyScreen(onNavigateUp: () -> Unit) { var policy by remember { mutableIntStateOf(DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY) } fun refresh() { policy = dpm.getContentProtectionPolicy(receiver) } LaunchedEffect(Unit) { refresh() } - MyScaffold(R.string.content_protection_policy, 8.dp, onNavigateUp) { + MyScaffold(R.string.content_protection_policy, 0.dp, onNavigateUp) { mapOf( DevicePolicyManager.CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY to R.string.not_controlled_by_policy, DevicePolicyManager.CONTENT_PROTECTION_ENABLED to R.string.enabled, DevicePolicyManager.CONTENT_PROTECTION_DISABLED to R.string.disabled ).forEach { (policyId, string) -> - RadioButtonItem(string, policy == policyId) { policy = policyId } + FullWidthRadioButtonItem(string, policy == policyId) { policy = policyId } } Button( onClick = { @@ -861,11 +862,11 @@ fun ContentProtectionPolicyScreen(onNavigateUp: () -> Unit) { refresh() context.showOperationResultToast(true) }, - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp) + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp) ) { Text(stringResource(R.string.apply)) } - InfoCard(R.string.info_content_protection_policy) + InfoCard(R.string.info_content_protection_policy, 8.dp) } } @@ -878,21 +879,27 @@ fun PermissionPolicyScreen(onNavigateUp: () -> Unit) { val dpm = context.getDPM() val receiver = context.getReceiver() var selectedPolicy by remember { mutableIntStateOf(dpm.getPermissionPolicy(receiver)) } - MyScaffold(R.string.permission_policy, 8.dp, onNavigateUp) { - RadioButtonItem(R.string.default_stringres, selectedPolicy == PERMISSION_POLICY_PROMPT) { selectedPolicy = PERMISSION_POLICY_PROMPT } - RadioButtonItem(R.string.auto_grant, selectedPolicy == PERMISSION_POLICY_AUTO_GRANT) { selectedPolicy = PERMISSION_POLICY_AUTO_GRANT } - RadioButtonItem(R.string.auto_deny, selectedPolicy == PERMISSION_POLICY_AUTO_DENY) { selectedPolicy = PERMISSION_POLICY_AUTO_DENY } + MyScaffold(R.string.permission_policy, 0.dp, onNavigateUp) { + FullWidthRadioButtonItem(R.string.default_stringres, selectedPolicy == PERMISSION_POLICY_PROMPT) { + selectedPolicy = PERMISSION_POLICY_PROMPT + } + FullWidthRadioButtonItem(R.string.auto_grant, selectedPolicy == PERMISSION_POLICY_AUTO_GRANT) { + selectedPolicy = PERMISSION_POLICY_AUTO_GRANT + } + FullWidthRadioButtonItem(R.string.auto_deny, selectedPolicy == PERMISSION_POLICY_AUTO_DENY) { + selectedPolicy = PERMISSION_POLICY_AUTO_DENY + } Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { dpm.setPermissionPolicy(receiver,selectedPolicy) context.showOperationResultToast(true) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp) ) { Text(stringResource(R.string.apply)) } - InfoCard(R.string.info_permission_policy) + InfoCard(R.string.info_permission_policy, 8.dp) } } @@ -904,10 +911,12 @@ fun MtePolicyScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val dpm = context.getDPM() var selectedMtePolicy by remember { mutableIntStateOf(dpm.mtePolicy) } - MyScaffold(R.string.mte_policy, 8.dp, onNavigateUp) { - RadioButtonItem(R.string.decide_by_user, selectedMtePolicy == MTE_NOT_CONTROLLED_BY_POLICY) { selectedMtePolicy = MTE_NOT_CONTROLLED_BY_POLICY } - RadioButtonItem(R.string.enabled, selectedMtePolicy == MTE_ENABLED) { selectedMtePolicy = MTE_ENABLED } - RadioButtonItem(R.string.disabled, selectedMtePolicy == MTE_DISABLED) { selectedMtePolicy = MTE_DISABLED } + MyScaffold(R.string.mte_policy, 0.dp, onNavigateUp) { + FullWidthRadioButtonItem(R.string.decide_by_user, selectedMtePolicy == MTE_NOT_CONTROLLED_BY_POLICY) { + selectedMtePolicy = MTE_NOT_CONTROLLED_BY_POLICY + } + FullWidthRadioButtonItem(R.string.enabled, selectedMtePolicy == MTE_ENABLED) { selectedMtePolicy = MTE_ENABLED } + FullWidthRadioButtonItem(R.string.disabled, selectedMtePolicy == MTE_DISABLED) { selectedMtePolicy = MTE_DISABLED } Button( onClick = { try { @@ -918,11 +927,11 @@ fun MtePolicyScreen(onNavigateUp: () -> Unit) { } selectedMtePolicy = dpm.mtePolicy }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp) ) { Text(stringResource(R.string.apply)) } - InfoCard(R.string.info_mte_policy) + InfoCard(R.string.info_mte_policy, 8.dp) } } @@ -934,62 +943,64 @@ fun NearbyStreamingPolicyScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val dpm = context.getDPM() var appPolicy by remember { mutableIntStateOf(dpm.nearbyAppStreamingPolicy) } - MyScaffold(R.string.nearby_streaming_policy, 8.dp, onNavigateUp) { - Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.nearby_app_streaming), style = typography.titleLarge) - Spacer(Modifier.padding(vertical = 3.dp)) - RadioButtonItem( + MyScaffold(R.string.nearby_streaming_policy, 0.dp, onNavigateUp, false) { + Text( + stringResource(R.string.nearby_app_streaming), + Modifier.padding(start = 8.dp, top = 10.dp, bottom = 4.dp), style = typography.titleLarge + ) + FullWidthRadioButtonItem( R.string.decide_by_user, appPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY ) { appPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY } - RadioButtonItem(R.string.enabled, appPolicy == NEARBY_STREAMING_ENABLED) { appPolicy = NEARBY_STREAMING_ENABLED } - RadioButtonItem(R.string.disabled, appPolicy == NEARBY_STREAMING_DISABLED) { appPolicy = NEARBY_STREAMING_DISABLED } - RadioButtonItem( + FullWidthRadioButtonItem(R.string.enabled, appPolicy == NEARBY_STREAMING_ENABLED) { appPolicy = NEARBY_STREAMING_ENABLED } + FullWidthRadioButtonItem(R.string.disabled, appPolicy == NEARBY_STREAMING_DISABLED) { appPolicy = NEARBY_STREAMING_DISABLED } + FullWidthRadioButtonItem( R.string.enable_if_secure_enough, appPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY ) { appPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY } - Spacer(Modifier.padding(vertical = 3.dp)) Button( onClick = { dpm.nearbyAppStreamingPolicy = appPolicy + appPolicy = dpm.nearbyAppStreamingPolicy context.showOperationResultToast(true) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp) ) { Text(stringResource(R.string.apply)) } - InfoCard(R.string.info_nearby_app_streaming_policy) + InfoCard(R.string.info_nearby_app_streaming_policy, 8.dp) var notificationPolicy by remember { mutableIntStateOf(dpm.nearbyNotificationStreamingPolicy) } - Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.nearby_notification_streaming), style = typography.titleLarge) - Spacer(Modifier.padding(vertical = 3.dp)) - RadioButtonItem( + Text( + stringResource(R.string.nearby_notification_streaming), + Modifier.padding(start = 8.dp, top = 10.dp, bottom = 4.dp), style = typography.titleLarge + ) + FullWidthRadioButtonItem( R.string.decide_by_user, notificationPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY ) { notificationPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY } - RadioButtonItem( + FullWidthRadioButtonItem( R.string.enabled, notificationPolicy == NEARBY_STREAMING_ENABLED ) { notificationPolicy = NEARBY_STREAMING_ENABLED } - RadioButtonItem( + FullWidthRadioButtonItem( R.string.disabled, notificationPolicy == NEARBY_STREAMING_DISABLED ) { notificationPolicy = NEARBY_STREAMING_DISABLED } - RadioButtonItem( + FullWidthRadioButtonItem( R.string.enable_if_secure_enough, notificationPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY ) { notificationPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY } - Spacer(Modifier.padding(vertical = 3.dp)) Button( onClick = { dpm.nearbyNotificationStreamingPolicy = notificationPolicy + notificationPolicy = dpm.nearbyNotificationStreamingPolicy context.showOperationResultToast(true) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp) ) { Text(stringResource(R.string.apply)) } - InfoCard(R.string.info_nearby_notification_streaming_policy) + InfoCard(R.string.info_nearby_notification_streaming_policy, 8.dp) } } @@ -1586,7 +1597,8 @@ fun FrpPolicyScreen(onNavigateUp: () -> Unit) { onClick = { accountList += inputAccount inputAccount = "" - } + }, + enabled = inputAccount.isNotBlank() ) { Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(R.string.add)) } @@ -1733,87 +1745,81 @@ fun WipeDataScreen(onNavigateUp: () -> Unit) { @Serializable object SetSystemUpdatePolicy +@RequiresApi(23) @Composable -fun SystemUpdatePolicy(onNavigateUp: () -> Unit) { +fun SystemUpdatePolicyScreen(onNavigateUp: () -> Unit) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current - MyScaffold(R.string.system_update_policy, 8.dp, onNavigateUp) { - if(VERSION.SDK_INT >= 23) { - Column { - var selectedPolicy by remember { mutableStateOf(dpm.systemUpdatePolicy?.policyType) } - RadioButtonItem( - R.string.system_update_policy_automatic, - selectedPolicy == TYPE_INSTALL_AUTOMATIC - ) { selectedPolicy = TYPE_INSTALL_AUTOMATIC } - RadioButtonItem( - R.string.system_update_policy_install_windowed, - selectedPolicy == TYPE_INSTALL_WINDOWED - ) { selectedPolicy = TYPE_INSTALL_WINDOWED } - RadioButtonItem( - R.string.system_update_policy_postpone, - selectedPolicy == TYPE_POSTPONE - ) { selectedPolicy = TYPE_POSTPONE } - RadioButtonItem(R.string.none, selectedPolicy == null) { selectedPolicy = null } - var windowedPolicyStart by remember { mutableStateOf("") } - var windowedPolicyEnd by remember { mutableStateOf("") } - AnimatedVisibility(selectedPolicy == 2) { - Column { - Row( - horizontalArrangement = Arrangement.SpaceBetween - ) { - OutlinedTextField( - value = windowedPolicyStart, - label = { Text(stringResource(R.string.start_time)) }, - onValueChange = { windowedPolicyStart = it }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), - modifier = Modifier.fillMaxWidth(0.49F) - ) - OutlinedTextField( - value = windowedPolicyEnd, - onValueChange = {windowedPolicyEnd = it }, - label = { Text(stringResource(R.string.end_time)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), - modifier = Modifier.fillMaxWidth(0.96F).padding(bottom = 2.dp) - ) - } - Text(text = stringResource(R.string.minutes_in_one_day)) - } - } - Button( - onClick = { - val policy = - when(selectedPolicy) { - TYPE_INSTALL_AUTOMATIC-> SystemUpdatePolicy.createAutomaticInstallPolicy() - TYPE_INSTALL_WINDOWED-> SystemUpdatePolicy.createWindowedInstallPolicy(windowedPolicyStart.toInt(), windowedPolicyEnd.toInt()) - TYPE_POSTPONE-> SystemUpdatePolicy.createPostponeInstallPolicy() - else -> null - } - dpm.setSystemUpdatePolicy(receiver,policy) - context.showOperationResultToast(true) - }, - modifier = Modifier.fillMaxWidth().padding(top = 8.dp) - ) { - Text(stringResource(R.string.apply)) + MyScaffold(R.string.system_update_policy, 0.dp, onNavigateUp) { + var selectedPolicy by remember { mutableStateOf(dpm.systemUpdatePolicy?.policyType) } + FullWidthRadioButtonItem( + R.string.system_update_policy_automatic, + selectedPolicy == TYPE_INSTALL_AUTOMATIC + ) { selectedPolicy = TYPE_INSTALL_AUTOMATIC } + FullWidthRadioButtonItem( + R.string.system_update_policy_install_windowed, + selectedPolicy == TYPE_INSTALL_WINDOWED + ) { selectedPolicy = TYPE_INSTALL_WINDOWED } + FullWidthRadioButtonItem( + R.string.system_update_policy_postpone, + selectedPolicy == TYPE_POSTPONE + ) { selectedPolicy = TYPE_POSTPONE } + FullWidthRadioButtonItem(R.string.none, selectedPolicy == null) { selectedPolicy = null } + var windowedPolicyStart by remember { mutableStateOf("") } + var windowedPolicyEnd by remember { mutableStateOf("") } + AnimatedVisibility(selectedPolicy == 2) { + Column(Modifier.padding(horizontal = 8.dp)) { + Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), Arrangement.SpaceBetween) { + OutlinedTextField( + value = windowedPolicyStart, + label = { Text(stringResource(R.string.start_time)) }, + onValueChange = { windowedPolicyStart = it }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + modifier = Modifier.fillMaxWidth(0.49F) + ) + OutlinedTextField( + value = windowedPolicyEnd, + onValueChange = {windowedPolicyEnd = it }, + label = { Text(stringResource(R.string.end_time)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + modifier = Modifier.fillMaxWidth(0.96F).padding(bottom = 2.dp) + ) } + Text(stringResource(R.string.minutes_in_one_day), color = colorScheme.onSurfaceVariant, style = typography.bodyMedium) } } + Button( + onClick = { + val policy = + when(selectedPolicy) { + TYPE_INSTALL_AUTOMATIC-> SystemUpdatePolicy.createAutomaticInstallPolicy() + TYPE_INSTALL_WINDOWED-> SystemUpdatePolicy.createWindowedInstallPolicy(windowedPolicyStart.toInt(), windowedPolicyEnd.toInt()) + TYPE_POSTPONE-> SystemUpdatePolicy.createPostponeInstallPolicy() + else -> null + } + dpm.setSystemUpdatePolicy(receiver,policy) + context.showOperationResultToast(true) + }, + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp) + ) { + Text(stringResource(R.string.apply)) + } if(VERSION.SDK_INT >= 26) { - Spacer(Modifier.padding(vertical = 10.dp)) val sysUpdateInfo = dpm.getPendingSystemUpdate(receiver) - Column { + Column(Modifier.padding(8.dp)) { if(sysUpdateInfo != null) { Text(text = stringResource(R.string.update_received_time, Date(sysUpdateInfo.receivedTime))) - val securityStateDesc = when(sysUpdateInfo.securityPatchState) { - SystemUpdateInfo.SECURITY_PATCH_STATE_UNKNOWN -> stringResource(R.string.unknown) - SystemUpdateInfo.SECURITY_PATCH_STATE_TRUE -> "true" - else->"false" + val securityPatchStateText = when(sysUpdateInfo.securityPatchState) { + SystemUpdateInfo.SECURITY_PATCH_STATE_FALSE -> R.string.no + SystemUpdateInfo.SECURITY_PATCH_STATE_TRUE -> R.string.yes + else -> R.string.unknown } - Text(text = stringResource(R.string.is_security_patch, securityStateDesc)) - }else{ + Text(text = stringResource(R.string.is_security_patch, stringResource(securityPatchStateText))) + } else { Text(text = stringResource(R.string.no_system_update)) } } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt index 8f90578..bad7931 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Users.kt @@ -118,7 +118,10 @@ fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { if(bitmap != null) changeUserIconDialog = true } } - FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { launcher.launch("image/*") } + FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { + Toast.makeText(context, R.string.select_an_image, Toast.LENGTH_SHORT).show() + launcher.launch("image/*") + } if(changeUserIconDialog == true) ChangeUserIconDialog(bitmap!!) { changeUserIconDialog = false } } if(VERSION.SDK_INT >= 28 && deviceOwner) { @@ -432,7 +435,8 @@ fun AffiliationIdScreen(onNavigateUp: () -> Unit) { onClick = { list += input input = "" - } + }, + enabled = input.isNotEmpty() ) { Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(R.string.add)) } @@ -453,7 +457,7 @@ fun AffiliationIdScreen(onNavigateUp: () -> Unit) { ) { Text(stringResource(R.string.apply)) } - InfoCard(R.string.info_affiliated_id) + InfoCard(R.string.info_affiliation_id) } } diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt index 15d423b..1f7fed9 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt @@ -272,12 +272,12 @@ fun ListItem(text: String, onDelete: () -> Unit) { } @Composable -fun InfoCard(@StringRes strID: Int) { +fun InfoCard(@StringRes strID: Int, horizonPadding: Dp = 0.dp) { Column( modifier = Modifier .fillMaxWidth() - .padding(vertical = 8.dp) - .clip(RoundedCornerShape(10)) + .padding(vertical = 8.dp, horizontal = horizonPadding) + .clip(RoundedCornerShape(12.dp)) .background(color = colorScheme.tertiaryContainer) .padding(8.dp) ) { diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index dd24f04..dde8a46 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -526,8 +526,7 @@ Серийный номер этого пользователя: %1$d Аффилированный идентификатор Изменить значок пользователя - Использовать выборщик файлов вместо галереи - Выберите изображение... + Select an image Ошибка: управляемый профиль Ошибка: текущий пользователь Сообщение о сеансе пользователя @@ -544,10 +543,10 @@ Время ожидания экрана Время ожидания строгой аутентификации Длина истории паролей - Нет (разрешено отсутствие пароля) - Низкая (графический ключ и повторение символов разрешены) - Средняя (повторение запрещено, минимум 4 символа) - Высокая (повторение запрещено, минимум 6 символов) + + Low + Medium + High Текущая сложность пароля Сложность пароля достаточна Единый пароль @@ -565,7 +564,6 @@ Требовать ввод Сбросить пароль с помощью токена Требуемая сложность пароля - Запрос на установку нового пароля Функции блокировки экрана (Keyguard) Включить все Отключить виджеты @@ -709,7 +707,8 @@ Установить список приложений, которые нужно сохранить в виде APK-файлов, даже если ни у одного пользователя в данный момент они не установлены. Режим "безголового" системного пользователя означает, что системный пользователь запускает системные службы и некоторый системный интерфейс, но он не связан с каким-либо реальным человеком, и для связи с реальными людьми должны быть созданы дополнительные пользователи. If the current user is not switched by OwnDroid, this function cannot be used. - Когда владелец устройства создает управляемого пользователя, управляемый пользователь не является аффилированным. Чтобы сделать управляемого пользователя аффилированным с владельцем устройства, вам следует установить одинаковые аффилированные идентификаторы в основном и управляемом пользователях. + Когда владелец устройства создает управляемого пользователя, управляемый пользователь не является аффилированным. Чтобы сделать управляемого пользователя аффилированным с владельцем устройства, вам следует установить одинаковые аффилированные идентификаторы в основном и управляемом пользователях. + None: no password\nLow: pattern, PIN (with repeating or ordered sequences)\nMedium: PIN (with no repeating or ordered sequences, length at least 4), alphabetic (length at least 4), alphanumeric (length at least 4)\nHigh: PIN (with no repeating or ordered sequences, length at least 8), alphabetic (length at least 6), alphanumeric (length at least 6) Установить новый пароль блокировки экрана. Длина этого пароля должна быть не менее 4 цифр. Оставьте поле пустым, чтобы удалить пароль.\nЕсли вы установите цифровой пароль длиной 6 символов или меньше, он будет установлен как PIN-код. Установить максимальное время бездействия пользователя, по истечении которого устройство будет заблокировано. Это ограничивает время, которое может установить пользователь.\nЗначение 0 означает отсутствие ограничений. Перезапустить отсчет времени истечения срока действия пароля.\nЗначение 0 означает отсутствие ограничений. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 2fcb450..d718694 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -530,8 +530,7 @@ Bu kullanıcının seri numarası: %1$d Bağlılık ID Kullanıcı simgesini değiştir - Galeri yerine dosya seçici kullan - Resim seç... + Select an image Başarısız: yönetilen profil Başarısız: mevcut kullanıcı Kullanıcı oturum mesajı @@ -546,10 +545,10 @@ Ekran zaman aşımı Gereken güçlü doğrulama zaman aşımı Şifre geçmişi uzunluğu - Yok (Şifreye izin verilmez) - Düşük (Hareket şifresi ve karakter tekrarı izinli) - Orta (Tekrar yasak, en az 4 karakter) - Yüksek (Tekrar yasak, en az 6 karakter) + + Low + Medium + High Mevcut şifre karmaşıklığı Şifre karmaşıklığı yeterli mi Birleşik şifre @@ -567,7 +566,6 @@ Giriş gerektir Jeton ile şifreyi sıfırla Gereken şifre karmaşıklığı - Yeni şifre ayarlanmasını iste Kilit ekranı özellikleri Tümünü etkinleştir Widget\'ı devre dışı bırak diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c012e27..30f019c 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -516,8 +516,7 @@ 新用户的序列号:%1$d 附属用户ID 更换用户头像 - 使用文件选择器而不是相册 - 选择图片... + 选择一个图片 失败:受管理的资料 失败:当前用户 用户会话消息 @@ -532,10 +531,9 @@ 屏幕超时 要求强验证超时 密码历史长度 - 无(允许不设密码) - 低(允许图案和连续性) - 中(无连续性,至少4位) - 高(无连续性,至少6位) + + + 当前密码复杂度 密码符合复杂度要求 一致的密码 @@ -553,7 +551,6 @@ 不允许其他设备管理员重置密码直至用户输入一次密码 使用令牌重置密码 密码复杂度要求 - 要求设置新密码 锁屏功能 启用全部 禁用小工具(安卓5以下) @@ -694,7 +691,8 @@ 这个列表中的应用的APK将会一直保留,即使没有任何用户安装这个应用 无头系统用户模式意味着系统用户运行系统服务和一些系统UI,但它不与任何真实的人相关联,必须创建额外的用户才能与真实的人相关联。 如果当前用户不是由OwnDroid切换的,无法使用此功能。 - 当Device owner创建并管理用户时,新的用户不是附属用户。Device owner设置和受管理用户完全相同的附属用户ID后,受管理用户成为附属于Device owner的用户 + 当Device owner创建并管理用户时,新的用户不是附属用户。Device owner设置和受管理用户完全相同的附属用户ID后,受管理用户成为附属于Device owner的用户 + 无:无密码\n低:图案,PIN(有重复或有序序列)\n中:PIN(没有重复或有序序列,最低长度4),字母(最低长度4),字母与数字(最低长度4)\n高:PIN(没有重复或有序序列,最低长度8),字母(最低长度6),字母与数字(最低长度6) 设置一个新的密码,密码的长度需要4位或以上,不输入密码将会清除现有的密码。长度在6位或以下的纯数字密码将会设置为PIN码。 设置设备锁定前用户活动的最大时间。这限制了用户可以设置的时间长度。\n值为0表示不做限制。 重新启动密码过期倒计时。值为0表示不做限制。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7213874..900512d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -555,8 +555,7 @@ Serial number of this user: %1$d Affiliation ID Change user icon - Use file picker instead of gallery - Select image... + Select an image Failed: managed profile Failed: current user User session message @@ -571,10 +570,9 @@ Screen timeout Required strong auth timeout Password history length - None (No password allowed) - Low (Gesture password and characters repetition allowed) - Medium (Repetition disallowed, 4 characters at least) - High (Repetition disallowed, 6 characters at least) + Low + Medium + High Current password complexity Password complexity sufficient Unified password @@ -592,7 +590,6 @@ Require entry Reset password with token Required password complexity - Request to set a new password Keyguard features Enable all Disable widget @@ -734,7 +731,8 @@ Set a list of apps to keep around as APKs even if no user has currently installed it. Headless system user mode means the system user runs system services and some system UI, but it is not associated with any real person and additional users must be created to be associated with real persons. If the current user is not switched by OwnDroid, this function cannot be used. - When Device owner create a managed user, the managed user isn\'t affiliated. In order to make the managed user affiliated with the Device owner, you should set same affiliated IDs in main user and managed user + When Device owner create a managed user, the managed user isn\'t affiliated. In order to make the managed user affiliated with the Device owner, you should set same affiliated IDs in main user and managed user + None: no password\nLow: pattern, PIN (with repeating or ordered sequences)\nMedium: PIN (with no repeating or ordered sequences, length at least 4), alphabetic (length at least 4), alphanumeric (length at least 4)\nHigh: PIN (with no repeating or ordered sequences, length at least 8), alphabetic (length at least 6), alphanumeric (length at least 6) Set a new lockscreen password. The length of this password must be at least 4 digits. Keep it empty to remove password.\nIf you set a numeric password that length is 6 or lower, it will set as PIN Set the maximum time for user activity until the device will lock. This limits the length that the user can set.\nA value of 0 means there is no restriction. Restart the countdown for password expiration.\nA value of 0 means there is no restriction.