Add delegated admin in a new screen

Some UI improvements
Info of password complexity
This commit is contained in:
BinTianqi
2025-02-16 16:13:44 +08:00
parent fd0073f907
commit 3d6e12581b
11 changed files with 299 additions and 260 deletions

View File

@@ -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<DeviceAdmin> { DeviceAdminScreen(::navigateUp) }
composable<ProfileOwner> { ProfileOwnerScreen(::navigateUp) }
composable<DeviceOwner> { DeviceOwnerScreen(::navigateUp) }
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp) }
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp, ::navigate) }
composable<AddDelegatedAdmin>{ AddDelegatedAdminScreen(it.toRoute(), ::navigateUp) }
composable<DeviceInfo> { DeviceInfoScreen(::navigateUp) }
composable<LockScreenInfo> { LockScreenInfoScreen(::navigateUp) }
composable<SupportMessage> { SupportMessageScreen(::navigateUp) }
composable<TransferOwnership> { TransferOwnershipScreen(::navigateUp) }
composable<SystemManager> { SystemManagerScreen(::navigateUp) { navController.navigate(it) } }
composable<SystemManager> { SystemManagerScreen(::navigateUp, ::navigate) }
composable<SystemOptions> { SystemOptionsScreen(::navigateUp) }
composable<Keyguard> { KeyguardScreen(::navigateUp) }
composable<HardwareMonitor> { HardwareMonitorScreen(::navigateUp) }
@@ -311,12 +315,12 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<CaCert> { CaCertScreen(::navigateUp) }
composable<SecurityLogging> { SecurityLoggingScreen(::navigateUp) }
composable<DisableAccountManagement> { DisableAccountManagementScreen(::navigateUp) }
composable<SetSystemUpdatePolicy> { SystemUpdatePolicy(::navigateUp) }
composable<SetSystemUpdatePolicy> { SystemUpdatePolicyScreen(::navigateUp) }
composable<InstallSystemUpdate> { InstallSystemUpdateScreen(::navigateUp) }
composable<FrpPolicy> { FrpPolicyScreen(::navigateUp) }
composable<WipeData> { WipeDataScreen(::navigateUp) }
composable<Network> { NetworkScreen(::navigateUp) { navController.navigate(it) } }
composable<Network> { NetworkScreen(::navigateUp, ::navigate) }
composable<WiFi> {
WifiScreen(::navigateUp, { navController.navigate(it) }) {
val dest = navController.graph.findNode(AddNetwork)!!.id
@@ -327,7 +331,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) }
composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::navigateUp) }
composable<WifiSsidPolicyScreen> { WifiSsidPolicyScreen(::navigateUp) }
composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp) { navController.navigate(it) } }
composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp, ::navigate) }
composable<NetworkStatsViewer>(mapOf(serializableNavTypePair<List<NetworkStatsViewer.Data>>())) {
NetworkStatsViewerScreen(it.toRoute()) { navController.navigateUp() }
}
@@ -339,7 +343,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<PreferentialNetworkService> { PreferentialNetworkServiceScreen(::navigateUp) }
composable<OverrideApn> { OverrideApnScreen(::navigateUp) }
composable<WorkProfile> { WorkProfileScreen(::navigateUp) { navController.navigate(it) } }
composable<WorkProfile> { WorkProfileScreen(::navigateUp, ::navigate) }
composable<OrganizationOwnedProfile> { OrganizationOwnedProfileScreen(::navigateUp) }
composable<CreateWorkProfile> { CreateWorkProfileScreen(::navigateUp) }
composable<SuspendPersonalApp> { SuspendPersonalAppScreen(::navigateUp) }
@@ -370,7 +374,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
}
}
composable<Users> { UsersScreen(::navigateUp) { navController.navigate(it) } }
composable<Users> { UsersScreen(::navigateUp, ::navigate) }
composable<UserInfo> { UserInfoScreen(::navigateUp) }
composable<UsersOptions> { UsersOptionsScreen(::navigateUp) }
composable<UserOperation> { UserOperationScreen(::navigateUp) }
@@ -379,7 +383,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<UserSessionMessage> { UserSessionMessageScreen(::navigateUp) }
composable<AffiliationId> { AffiliationIdScreen(::navigateUp) }
composable<Password> { PasswordScreen(::navigateUp) { navController.navigate(it) } }
composable<Password> { PasswordScreen(::navigateUp, ::navigate) }
composable<PasswordInfo> { PasswordInfoScreen(::navigateUp) }
composable<ResetPasswordToken> { ResetPasswordTokenScreen(::navigateUp) }
composable<ResetPassword> { ResetPasswordScreen(::navigateUp) }
@@ -387,7 +391,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<KeyguardDisabledFeatures> { KeyguardDisabledFeaturesScreen(::navigateUp) }
composable<RequiredPasswordQuality> { RequiredPasswordQualityScreen(::navigateUp) }
composable<Settings> { SettingsScreen(::navigateUp) { navController.navigate(it) } }
composable<Settings> { SettingsScreen(::navigateUp, ::navigate) }
composable<SettingsOptions> { SettingsOptionsScreen(::navigateUp) }
composable<Appearance> {
val theme by vm.theme.collectAsStateWithLifecycle()

View File

@@ -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<NetworkStats.Bucket> {
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(

View File

@@ -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))
}

View File

@@ -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<String>()) }
val packages = remember { mutableStateMapOf<String, MutableList<DelegatedScope>>() }
fun refresh() {
val list = mutableMapOf<String, MutableList<DelegatedScope>>()
@@ -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<DelegatedScope> = 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))
}
}
}

View File

@@ -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))
}
}

View File

@@ -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)
}
}

View File

@@ -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)
) {

View File

@@ -526,8 +526,7 @@
<string name="serial_number_of_new_user_is">Серийный номер этого пользователя: %1$d</string>
<string name="affiliation_id">Аффилированный идентификатор</string>
<string name="change_user_icon">Изменить значок пользователя</string>
<string name="file_picker_instead_gallery">Использовать выборщик файлов вместо галереи</string>
<string name="select_picture" tools:ignore="TypographyEllipsis">Выберите изображение...</string>
<string name="select_an_image">Select an image</string> <!--TODO-->
<string name="fail_managed_profile">Ошибка: управляемый профиль</string>
<string name="fail_current_user">Ошибка: текущий пользователь</string>
<string name="user_session_msg">Сообщение о сеансе пользователя</string>
@@ -544,10 +543,10 @@
<string name="max_time_to_lock">Время ожидания экрана</string>
<string name="required_strong_auth_timeout">Время ожидания строгой аутентификации</string>
<string name="pwd_history">Длина истории паролей</string>
<string name="password_complexity_none">Нет (разрешено отсутствие пароля)</string>
<string name="password_complexity_low">Низкая (графический ключ и повторение символов разрешены)</string>
<string name="password_complexity_medium">Средняя (повторение запрещено, минимум 4 символа)</string>
<string name="password_complexity_high">Высокая (повторение запрещено, минимум 6 символов)</string>
<!--TODO: 3 strings-->
<string name="low">Low</string>
<string name="medium">Medium</string>
<string name="high">High</string>
<string name="current_password_complexity">Текущая сложность пароля</string>
<string name="password_sufficient">Сложность пароля достаточна</string>
<string name="unified_password">Единый пароль</string>
@@ -565,7 +564,6 @@
<string name="reset_password_require_entry">Требовать ввод</string>
<string name="reset_password_with_token">Сбросить пароль с помощью токена</string>
<string name="required_password_complexity">Требуемая сложность пароля</string>
<string name="require_set_new_password">Запрос на установку нового пароля</string>
<string name="disable_keyguard_features">Функции блокировки экрана (Keyguard)</string>
<string name="enable_all">Включить все</string>
<string name="disable_keyguard_features_widgets">Отключить виджеты</string>
@@ -709,7 +707,8 @@
<string name="info_keep_uninstalled_apps">Установить список приложений, которые нужно сохранить в виде APK-файлов, даже если ни у одного пользователя в данный момент они не установлены.</string>
<string name="info_headless_system_user_mode">Режим "безголового" системного пользователя означает, что системный пользователь запускает системные службы и некоторый системный интерфейс, но он не связан с каким-либо реальным человеком, и для связи с реальными людьми должны быть созданы дополнительные пользователи.</string>
<string name="info_logout">If the current user is not switched by OwnDroid, this function cannot be used.</string> <!--TODO-->
<string name="info_affiliated_id">Когда владелец устройства создает управляемого пользователя, управляемый пользователь не является аффилированным. Чтобы сделать управляемого пользователя аффилированным с владельцем устройства, вам следует установить одинаковые аффилированные идентификаторы в основном и управляемом пользователях.</string>
<string name="info_affiliation_id">Когда владелец устройства создает управляемого пользователя, управляемый пользователь не является аффилированным. Чтобы сделать управляемого пользователя аффилированным с владельцем устройства, вам следует установить одинаковые аффилированные идентификаторы в основном и управляемом пользователях.</string>
<!--TODO--><string name="info_password_complexity">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)</string>
<string name="info_reset_password">Установить новый пароль блокировки экрана. Длина этого пароля должна быть не менее 4 цифр. Оставьте поле пустым, чтобы удалить пароль.\nЕсли вы установите цифровой пароль длиной 6 символов или меньше, он будет установлен как PIN-код.</string>
<string name="info_screen_timeout">Установить максимальное время бездействия пользователя, по истечении которого устройство будет заблокировано. Это ограничивает время, которое может установить пользователь.\nЗначение 0 означает отсутствие ограничений.</string>
<string name="info_password_expiration_timeout">Перезапустить отсчет времени истечения срока действия пароля.\nЗначение 0 означает отсутствие ограничений.</string>

View File

@@ -530,8 +530,7 @@
<string name="serial_number_of_new_user_is">Bu kullanıcının seri numarası: %1$d</string>
<string name="affiliation_id">Bağlılık ID</string>
<string name="change_user_icon">Kullanıcı simgesini değiştir</string>
<string name="file_picker_instead_gallery">Galeri yerine dosya seçici kullan</string>
<string name="select_picture" tools:ignore="TypographyEllipsis">Resim seç...</string>
<string name="select_an_image">Select an image</string> <!--TODO-->
<string name="fail_managed_profile">Başarısız: yönetilen profil</string>
<string name="fail_current_user">Başarısız: mevcut kullanıcı</string>
<string name="user_session_msg">Kullanıcı oturum mesajı</string>
@@ -546,10 +545,10 @@
<string name="max_time_to_lock">Ekran zaman aşımı</string>
<string name="required_strong_auth_timeout">Gereken güçlü doğrulama zaman aşımı</string>
<string name="pwd_history">Şifre geçmişi uzunluğu</string>
<string name="password_complexity_none">Yok (Şifreye izin verilmez)</string>
<string name="password_complexity_low">Düşük (Hareket şifresi ve karakter tekrarı izinli)</string>
<string name="password_complexity_medium">Orta (Tekrar yasak, en az 4 karakter)</string>
<string name="password_complexity_high">Yüksek (Tekrar yasak, en az 6 karakter)</string>
<!--TODO: 3 strings-->
<string name="low">Low</string>
<string name="medium">Medium</string>
<string name="high">High</string>
<string name="current_password_complexity">Mevcut şifre karmaşıklığı</string>
<string name="password_sufficient">Şifre karmaşıklığı yeterli mi</string>
<string name="unified_password">Birleşik şifre</string>
@@ -567,7 +566,6 @@
<string name="reset_password_require_entry">Giriş gerektir</string>
<string name="reset_password_with_token">Jeton ile şifreyi sıfırla</string>
<string name="required_password_complexity">Gereken şifre karmaşıklığı</string>
<string name="require_set_new_password">Yeni şifre ayarlanmasını iste</string>
<string name="disable_keyguard_features">Kilit ekranı özellikleri</string>
<string name="enable_all">Tümünü etkinleştir</string>
<string name="disable_keyguard_features_widgets">Widget\'ı devre dışı bırak</string>

View File

@@ -516,8 +516,7 @@
<string name="serial_number_of_new_user_is">新用户的序列号:%1$d</string>
<string name="affiliation_id">附属用户ID</string>
<string name="change_user_icon">更换用户头像</string>
<string name="file_picker_instead_gallery">使用文件选择器而不是相册</string>
<string name="select_picture" tools:ignore="TypographyEllipsis">选择图片...</string>
<string name="select_an_image">选择一个图片</string>
<string name="fail_managed_profile">失败:受管理的资料</string>
<string name="fail_current_user">失败:当前用户</string>
<string name="user_session_msg">用户会话消息</string>
@@ -532,10 +531,9 @@
<string name="max_time_to_lock">屏幕超时</string>
<string name="required_strong_auth_timeout">要求强验证超时</string>
<string name="pwd_history">密码历史长度</string>
<string name="password_complexity_none">无(允许不设密码)</string>
<string name="password_complexity_low">低(允许图案和连续性)</string>
<string name="password_complexity_medium">无连续性至少4位</string>
<string name="password_complexity_high">无连续性至少6位</string>
<string name="low"></string>
<string name="medium"></string>
<string name="high"></string>
<string name="current_password_complexity">当前密码复杂度</string>
<string name="password_sufficient">密码符合复杂度要求</string>
<string name="unified_password">一致的密码</string>
@@ -553,7 +551,6 @@
<string name="reset_password_require_entry">不允许其他设备管理员重置密码直至用户输入一次密码</string>
<string name="reset_password_with_token">使用令牌重置密码</string>
<string name="required_password_complexity">密码复杂度要求</string>
<string name="require_set_new_password">要求设置新密码</string>
<string name="disable_keyguard_features">锁屏功能</string>
<string name="enable_all">启用全部</string>
<string name="disable_keyguard_features_widgets">禁用小工具(安卓5以下)</string>
@@ -694,7 +691,8 @@
<string name="info_keep_uninstalled_apps">这个列表中的应用的APK将会一直保留即使没有任何用户安装这个应用</string>
<string name="info_headless_system_user_mode">无头系统用户模式意味着系统用户运行系统服务和一些系统UI但它不与任何真实的人相关联必须创建额外的用户才能与真实的人相关联。</string>
<string name="info_logout">如果当前用户不是由OwnDroid切换的无法使用此功能。</string>
<string name="info_affiliated_id">当Device owner创建并管理用户时新的用户不是附属用户。Device owner设置和受管理用户完全相同的附属用户ID后受管理用户成为附属于Device owner的用户</string>
<string name="info_affiliation_id">当Device owner创建并管理用户时新的用户不是附属用户。Device owner设置和受管理用户完全相同的附属用户ID后受管理用户成为附属于Device owner的用户</string>
<string name="info_password_complexity">无:无密码\n低图案PIN有重复或有序序列\n中PIN没有重复或有序序列最低长度4字母最低长度4字母与数字最低长度4\n高PIN没有重复或有序序列最低长度8字母最低长度6字母与数字最低长度6</string>
<string name="info_reset_password">设置一个新的密码密码的长度需要4位或以上不输入密码将会清除现有的密码。长度在6位或以下的纯数字密码将会设置为PIN码。</string>
<string name="info_screen_timeout">设置设备锁定前用户活动的最大时间。这限制了用户可以设置的时间长度。\n值为0表示不做限制。</string>
<string name="info_password_expiration_timeout">重新启动密码过期倒计时。值为0表示不做限制。</string>

View File

@@ -555,8 +555,7 @@
<string name="serial_number_of_new_user_is">Serial number of this user: %1$d</string>
<string name="affiliation_id">Affiliation ID</string>
<string name="change_user_icon">Change user icon</string>
<string name="file_picker_instead_gallery">Use file picker instead of gallery</string>
<string name="select_picture" tools:ignore="TypographyEllipsis">Select image...</string>
<string name="select_an_image">Select an image</string>
<string name="fail_managed_profile">Failed: managed profile</string>
<string name="fail_current_user">Failed: current user</string>
<string name="user_session_msg">User session message</string>
@@ -571,10 +570,9 @@
<string name="max_time_to_lock">Screen timeout</string>
<string name="required_strong_auth_timeout">Required strong auth timeout</string>
<string name="pwd_history">Password history length</string>
<string name="password_complexity_none">None (No password allowed)</string>
<string name="password_complexity_low">Low (Gesture password and characters repetition allowed)</string>
<string name="password_complexity_medium">Medium (Repetition disallowed, 4 characters at least)</string>
<string name="password_complexity_high">High (Repetition disallowed, 6 characters at least)</string>
<string name="low">Low</string>
<string name="medium">Medium</string>
<string name="high">High</string>
<string name="current_password_complexity">Current password complexity</string>
<string name="password_sufficient">Password complexity sufficient</string>
<string name="unified_password">Unified password</string>
@@ -592,7 +590,6 @@
<string name="reset_password_require_entry">Require entry</string>
<string name="reset_password_with_token">Reset password with token</string>
<string name="required_password_complexity">Required password complexity</string>
<string name="require_set_new_password">Request to set a new password</string>
<string name="disable_keyguard_features">Keyguard features</string>
<string name="enable_all">Enable all</string>
<string name="disable_keyguard_features_widgets">Disable widget</string>
@@ -734,7 +731,8 @@
<string name="info_keep_uninstalled_apps">Set a list of apps to keep around as APKs even if no user has currently installed it. </string>
<string name="info_headless_system_user_mode">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.</string>
<string name="info_logout">If the current user is not switched by OwnDroid, this function cannot be used.</string>
<string name="info_affiliated_id">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</string>
<string name="info_affiliation_id">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</string>
<string name="info_password_complexity">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)</string>
<string name="info_reset_password">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</string>
<string name="info_screen_timeout">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.</string>
<string name="info_password_expiration_timeout">Restart the countdown for password expiration.\nA value of 0 means there is no restriction.</string>