diff --git a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt index ebebdee..b060f42 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MainActivity.kt @@ -177,7 +177,6 @@ import com.bintianqi.owndroid.dpm.ResetPassword import com.bintianqi.owndroid.dpm.ResetPasswordScreen import com.bintianqi.owndroid.dpm.ResetPasswordToken import com.bintianqi.owndroid.dpm.ResetPasswordTokenScreen -import com.bintianqi.owndroid.dpm.Restriction import com.bintianqi.owndroid.dpm.SecurityLogging import com.bintianqi.owndroid.dpm.SecurityLoggingScreen import com.bintianqi.owndroid.dpm.SetDefaultDialer @@ -216,8 +215,6 @@ import com.bintianqi.owndroid.dpm.UsersOptions import com.bintianqi.owndroid.dpm.UsersOptionsScreen import com.bintianqi.owndroid.dpm.UsersScreen import com.bintianqi.owndroid.dpm.WiFi -import com.bintianqi.owndroid.dpm.WifiAuthKeypair -import com.bintianqi.owndroid.dpm.WifiAuthKeypairScreen import com.bintianqi.owndroid.dpm.WifiScreen import com.bintianqi.owndroid.dpm.WifiSecurityLevel import com.bintianqi.owndroid.dpm.WifiSecurityLevelScreen @@ -601,12 +598,24 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) { ::navigateUp) } - composable { PasswordScreen(::navigateUp, ::navigate) } - composable { PasswordInfoScreen(::navigateUp) } - composable { ResetPasswordTokenScreen(::navigateUp) } - composable { ResetPasswordScreen(::navigateUp) } - composable { RequiredPasswordComplexityScreen(::navigateUp) } - composable { KeyguardDisabledFeaturesScreen(::navigateUp) } + composable { PasswordScreen(vm, ::navigateUp, ::navigate) } + composable { + PasswordInfoScreen(vm::getPasswordComplexity, vm::isPasswordComplexitySufficient, + vm::isUsingUnifiedPassword, ::navigateUp) + } + composable { + ResetPasswordTokenScreen(vm::getRpTokenState, vm::setRpToken, + vm::createActivateRpTokenIntent, vm::clearRpToken, ::navigateUp) + } + composable { ResetPasswordScreen(vm::resetPassword, ::navigateUp) } + composable { + RequiredPasswordComplexityScreen(vm::getRequiredPasswordComplexity, + vm::setRequiredPasswordComplexity, ::navigateUp) + } + composable { + KeyguardDisabledFeaturesScreen(vm::getKeyguardDisableConfig, + vm::setKeyguardDisableConfig, ::navigateUp) + } composable { RequiredPasswordQualityScreen(::navigateUp) } composable { SettingsScreen(::navigateUp, ::navigate) } diff --git a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt index a127451..3ab7410 100644 --- a/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt +++ b/app/src/main/java/com/bintianqi/owndroid/MyViewModel.kt @@ -1,9 +1,9 @@ package com.bintianqi.owndroid import android.accounts.Account -import android.annotation.SuppressLint import android.app.ActivityOptions import android.app.Application +import android.app.KeyguardManager import android.app.PendingIntent import android.app.admin.DeviceAdminInfo import android.app.admin.DeviceAdminReceiver @@ -68,15 +68,20 @@ import com.bintianqi.owndroid.dpm.HardwareProperties import com.bintianqi.owndroid.dpm.IntentFilterDirection import com.bintianqi.owndroid.dpm.IntentFilterOptions import com.bintianqi.owndroid.dpm.IpMode +import com.bintianqi.owndroid.dpm.KeyguardDisableConfig +import com.bintianqi.owndroid.dpm.KeyguardDisableMode import com.bintianqi.owndroid.dpm.NetworkStatsData import com.bintianqi.owndroid.dpm.NetworkStatsTarget +import com.bintianqi.owndroid.dpm.PasswordComplexity import com.bintianqi.owndroid.dpm.PendingSystemUpdateInfo import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceInfo import com.bintianqi.owndroid.dpm.PrivateDnsConfiguration +import com.bintianqi.owndroid.dpm.PrivateDnsMode import com.bintianqi.owndroid.dpm.ProxyMode import com.bintianqi.owndroid.dpm.ProxyType import com.bintianqi.owndroid.dpm.QueryNetworkStatsParams import com.bintianqi.owndroid.dpm.RecommendedProxyConf +import com.bintianqi.owndroid.dpm.RpTokenState import com.bintianqi.owndroid.dpm.SsidPolicy import com.bintianqi.owndroid.dpm.SsidPolicyType import com.bintianqi.owndroid.dpm.SystemOptionsStatus @@ -113,7 +118,6 @@ import java.time.ZoneId import java.time.ZonedDateTime import java.util.concurrent.Executors import kotlin.reflect.jvm.jvmErasure -import kotlin.system.measureTimeMillis class MyViewModel(application: Application): AndroidViewModel(application) { val myRepo = getApplication().myRepo @@ -1472,8 +1476,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) { } @RequiresApi(29) fun getPrivateDns(): PrivateDnsConfiguration { + val mode = DPM.getGlobalPrivateDnsMode(DAR) return PrivateDnsConfiguration( - DPM.getGlobalPrivateDnsMode(DAR), DPM.getGlobalPrivateDnsHost(DAR) ?: "" + PrivateDnsMode.entries.find { it.id == mode }!!, DPM.getGlobalPrivateDnsHost(DAR) ?: "" ) } @Suppress("PrivateApi") @@ -1483,7 +1488,8 @@ class MyViewModel(application: Application): AndroidViewModel(application) { val field = DevicePolicyManager::class.java.getDeclaredField("mService") field.isAccessible = true val dpm = field.get(DPM) as IDevicePolicyManager - val result = dpm.setGlobalPrivateDns(DAR, conf.mode, conf.host) + val host = if (conf.mode == PrivateDnsMode.Host) conf.host else null + val result = dpm.setGlobalPrivateDns(DAR, conf.mode.id, host) result == DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR } catch (e: Exception) { e.printStackTrace() @@ -1645,6 +1651,107 @@ class MyViewModel(application: Application): AndroidViewModel(application) { fun removeApnConfig(id: Int): Boolean { return DPM.removeOverrideApn(DAR, id) } + + @RequiresApi(29) + fun getPasswordComplexity(): PasswordComplexity { + val complexity = DPM.passwordComplexity + return PasswordComplexity.entries.find { it.id == complexity }!! + } + fun isPasswordComplexitySufficient(): Boolean { + return DPM.isActivePasswordSufficient + } + @RequiresApi(28) + fun isUsingUnifiedPassword(): Boolean { + return DPM.isUsingUnifiedPassword(DAR) + } + // Reset password token + @RequiresApi(26) + fun getRpTokenState(): RpTokenState { + return try { + RpTokenState(true, DPM.isResetPasswordTokenActive(DAR)) + } catch (_: IllegalArgumentException) { + RpTokenState(false, false) + } + } + @RequiresApi(26) + fun setRpToken(token: String): Boolean { + return DPM.setResetPasswordToken(DAR, token.encodeToByteArray()) + } + @RequiresApi(26) + fun clearRpToken(): Boolean { + return DPM.clearResetPasswordToken(DAR) + } + @RequiresApi(26) + fun createActivateRpTokenIntent(): Intent? { + val km = application.getSystemService(KeyguardManager::class.java) + val title = application.getString(R.string.activate_reset_password_token) + return km.createConfirmDeviceCredentialIntent(title, "") + } + fun resetPassword(password: String, token: String, flags: Int): Boolean { + return if (VERSION.SDK_INT >= 26) { + DPM.resetPasswordWithToken(DAR, password, token.encodeToByteArray(), flags) + } else { + DPM.resetPassword(password, flags) + } + } + @RequiresApi(31) + fun getRequiredPasswordComplexity(): PasswordComplexity { + val complexity = DPM.requiredPasswordComplexity + return PasswordComplexity.entries.find { it.id == complexity }!! + } + @RequiresApi(31) + fun setRequiredPasswordComplexity(complexity: PasswordComplexity) { + DPM.requiredPasswordComplexity = complexity.id + } + fun getKeyguardDisableConfig(): KeyguardDisableConfig { + val flags = DPM.getKeyguardDisabledFeatures(DAR) + val mode = when (flags) { + DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE -> KeyguardDisableMode.None + DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL -> KeyguardDisableMode.All + else -> KeyguardDisableMode.Custom + } + return KeyguardDisableConfig(mode, flags) + } + fun setKeyguardDisableConfig(config: KeyguardDisableConfig) { + val flags = when (config.mode) { + KeyguardDisableMode.None -> DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE + KeyguardDisableMode.All -> DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL + else -> config.flags + } + DPM.setKeyguardDisabledFeatures(DAR, flags) + } + fun getMaxTimeToLock(): Long { + return DPM.getMaximumTimeToLock(DAR) + } + @RequiresApi(26) + fun getRequiredStrongAuthTimeout(): Long { + return DPM.getRequiredStrongAuthTimeout(DAR) + } + fun getPasswordExpirationTimeout(): Long { + return DPM.getPasswordExpirationTimeout(DAR) + } + fun getMaxFailedPasswordsForWipe(): Int { + return DPM.getMaximumFailedPasswordsForWipe(DAR) + } + fun getPasswordHistoryLength(): Int { + return DPM.getPasswordHistoryLength(DAR) + } + fun setMaxTimeToLock(time: Long) { + DPM.setMaximumTimeToLock(DAR, time) + } + @RequiresApi(26) + fun setRequiredStrongAuthTimeout(time: Long) { + DPM.setRequiredStrongAuthTimeout(DAR, time) + } + fun setPasswordExpirationTimeout(time: Long) { + DPM.setPasswordExpirationTimeout(DAR, time) + } + fun setMaxFailedPasswordsForWipe(times: Int) { + DPM.setMaximumFailedPasswordsForWipe(DAR, times) + } + fun setPasswordHistoryLength(length: Int) { + DPM.setPasswordHistoryLength(DAR, length) + } } data class ThemeSettings( diff --git a/app/src/main/java/com/bintianqi/owndroid/UserRestrictionsRepository.kt b/app/src/main/java/com/bintianqi/owndroid/UserRestrictionsRepository.kt index 7caee10..8f18f52 100644 --- a/app/src/main/java/com/bintianqi/owndroid/UserRestrictionsRepository.kt +++ b/app/src/main/java/com/bintianqi/owndroid/UserRestrictionsRepository.kt @@ -88,7 +88,7 @@ object UserRestrictionsRepository { ) fun getData(id: String): Pair> { - val category = UserRestrictionCategory.entries.find { it.id == id }!! + val category = UserRestrictionCategory.valueOf(id) return category.title to when (category) { UserRestrictionCategory.Network -> network UserRestrictionCategory.Connectivity -> connectivity @@ -100,11 +100,11 @@ object UserRestrictionsRepository { } } -enum class UserRestrictionCategory(val id: String, val title: Int, val icon: Int) { - Network("network", R.string.network, R.drawable.language_fill0), - Connectivity("connectivity", R.string.connectivity, R.drawable.devices_other_fill0), - Applications("applications", R.string.applications, R.drawable.apps_fill0), - Media("media", R.string.media, R.drawable.volume_up_fill0), - Users("users", R.string.users, R.drawable.manage_accounts_fill0), - Other("other", R.string.other, R.drawable.more_horiz_fill0) +enum class UserRestrictionCategory(val title: Int, val icon: Int) { + Network(R.string.network, R.drawable.language_fill0), + Connectivity(R.string.connectivity, R.drawable.devices_other_fill0), + Applications(R.string.applications, R.drawable.apps_fill0), + Media(R.string.media, R.drawable.volume_up_fill0), + Users(R.string.users, R.drawable.manage_accounts_fill0), + Other(R.string.other, R.drawable.more_horiz_fill0) } \ No newline at end of file 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 c5c2e41..1f4cef1 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt @@ -1346,12 +1346,11 @@ fun NetworkStatsViewerScreen( @RequiresApi(29) enum class PrivateDnsMode(val id: Int, val text: Int) { - Off(DevicePolicyManager.PRIVATE_DNS_MODE_OFF, R.string.off), Opportunistic(DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC, R.string.automatic), Host(DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, R.string.enabled) } -data class PrivateDnsConfiguration(val mode: Int, val host: String) +data class PrivateDnsConfiguration(val mode: PrivateDnsMode, val host: String) @Serializable object PrivateDns @@ -1363,11 +1362,11 @@ fun PrivateDnsScreen( ) { val context = LocalContext.current val focusMgr = LocalFocusManager.current - var mode by remember { mutableStateOf(PrivateDnsMode.Off) } + var mode by remember { mutableStateOf(PrivateDnsMode.Opportunistic) } var inputHost by remember { mutableStateOf("") } LaunchedEffect(Unit) { val conf = getPrivateDns() - mode = PrivateDnsMode.entries.find { it.id == conf.mode } ?: PrivateDnsMode.Off + mode = conf.mode inputHost = conf.host } MyScaffold(R.string.private_dns, onNavigateUp, 0.dp) { @@ -1383,7 +1382,7 @@ fun PrivateDnsScreen( Button( onClick = { focusMgr.clearFocus() - val result = setPrivateDns(PrivateDnsConfiguration(mode.id, inputHost)) + val result = setPrivateDns(PrivateDnsConfiguration(mode, inputHost)) context.showOperationResultToast(result) }, modifier = Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding) 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 42d0e78..6f957cd 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt @@ -1,23 +1,8 @@ package com.bintianqi.owndroid.dpm import android.annotation.SuppressLint -import android.app.KeyguardManager -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 -import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE -import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT -import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_IRIS -import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA -import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS -import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL -import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS -import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS -import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL -import android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH -import android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW -import android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM -import android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE +import android.app.Activity +import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK @@ -28,15 +13,17 @@ 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 androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions @@ -63,9 +50,9 @@ import androidx.compose.ui.text.input.ImeAction 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.lifecycle.compose.collectAsStateWithLifecycle import com.bintianqi.owndroid.HorizontalPadding +import com.bintianqi.owndroid.MyViewModel import com.bintianqi.owndroid.Privilege import com.bintianqi.owndroid.R import com.bintianqi.owndroid.SP @@ -86,13 +73,13 @@ import kotlinx.serialization.Serializable @SuppressLint("NewApi") @Composable -fun PasswordScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { +fun PasswordScreen(vm: MyViewModel,onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { val context = LocalContext.current val privilege by Privilege.status.collectAsStateWithLifecycle() var dialog by remember { mutableIntStateOf(0) } MyScaffold(R.string.password_and_keyguard, onNavigateUp, 0.dp) { FunctionItem(R.string.password_info, icon = R.drawable.info_fill0) { onNavigate(PasswordInfo) } - if(SP.displayDangerousFeatures) { + if (SP.displayDangerousFeatures) { if(VERSION.SDK_INT >= 26) { FunctionItem(R.string.reset_password_token, icon = R.drawable.key_vertical_fill0) { onNavigate(ResetPasswordToken) } } @@ -118,14 +105,14 @@ fun PasswordScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { if(dialog != 0) { var input by remember { mutableStateOf("") } LaunchedEffect(Unit) { - input = when(dialog) { - 1 -> Privilege.DPM.getMaximumTimeToLock(Privilege.DAR).toString() - 2 -> Privilege.DPM.getRequiredStrongAuthTimeout(Privilege.DAR).toString() - 3 -> Privilege.DPM.getPasswordExpirationTimeout(Privilege.DAR).toString() - 4 -> Privilege.DPM.getMaximumFailedPasswordsForWipe(Privilege.DAR).toString() - 5 -> Privilege.DPM.getPasswordHistoryLength(Privilege.DAR).toString() - else -> "" - } + input = when (dialog) { + 1 -> vm.getMaxTimeToLock() + 2 -> vm.getRequiredStrongAuthTimeout() + 3 -> vm.getPasswordExpirationTimeout() + 4 -> vm.getMaxFailedPasswordsForWipe() + 5 -> vm.getPasswordHistoryLength() + else -> 0 + }.toString() } AlertDialog( title = { @@ -178,14 +165,15 @@ fun PasswordScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { TextButton( onClick = { when(dialog) { - 1 -> Privilege.DPM.setMaximumTimeToLock(Privilege.DAR, input.toLong()) - 2 -> Privilege.DPM.setRequiredStrongAuthTimeout(Privilege.DAR, input.toLong()) - 3 -> Privilege.DPM.setPasswordExpirationTimeout(Privilege.DAR, input.toLong()) - 4 -> Privilege.DPM.setMaximumFailedPasswordsForWipe(Privilege.DAR, input.toInt()) - 5 -> Privilege.DPM.setPasswordHistoryLength(Privilege.DAR, input.toInt()) + 1 -> vm.setMaxTimeToLock(input.toLong()) + 2 -> vm.setRequiredStrongAuthTimeout(input.toLong()) + 3 -> vm.setPasswordExpirationTimeout(input.toLong()) + 4 -> vm.setMaxFailedPasswordsForWipe(input.toInt()) + 5 -> vm.setPasswordHistoryLength(input.toInt()) } dialog = 0 - } + }, + enabled = input.toLongOrNull() != null ) { Text(stringResource(R.string.apply)) } @@ -202,26 +190,30 @@ fun PasswordScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { } } +@RequiresApi(29) +enum class PasswordComplexity(val id: Int, val text: Int) { + None(DevicePolicyManager.PASSWORD_COMPLEXITY_NONE, R.string.none), + Low(DevicePolicyManager.PASSWORD_COMPLEXITY_LOW, R.string.low), + Medium(DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM, R.string.medium), + High(DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH, R.string.high) +} + @Serializable object PasswordInfo @Composable -fun PasswordInfoScreen(onNavigateUp: () -> Unit) { +fun PasswordInfoScreen( + getComplexity: () -> PasswordComplexity, isSufficient: () -> Boolean, isUnified: () -> Boolean, + onNavigateUp: () -> Unit +) { val privilege by Privilege.status.collectAsStateWithLifecycle() var dialog by remember { mutableIntStateOf(0) } // 0:none, 1:password complexity MyScaffold(R.string.password_info, onNavigateUp, 0.dp) { - if(VERSION.SDK_INT >= 29) { - val text = when(Privilege.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 - } - InfoItem(R.string.current_password_complexity, text, true) { dialog = 1 } + if (VERSION.SDK_INT >= 29) { + InfoItem(R.string.current_password_complexity, getComplexity().text, true) { dialog = 1 } } - InfoItem(R.string.password_sufficient, Privilege.DPM.isActivePasswordSufficient.yesOrNo) + InfoItem(R.string.password_sufficient, isSufficient().yesOrNo) if(VERSION.SDK_INT >= 28 && privilege.work) { - InfoItem(R.string.unified_password, Privilege.DPM.isUsingUnifiedPassword(Privilege.DAR).yesOrNo) + InfoItem(R.string.unified_password, isUnified().yesOrNo) } } if(dialog != 0) AlertDialog( @@ -235,62 +227,59 @@ fun PasswordInfoScreen(onNavigateUp: () -> Unit) { ) } +data class RpTokenState(val set: Boolean, val active: Boolean) + @Serializable object ResetPasswordToken @RequiresApi(26) @Composable -fun ResetPasswordTokenScreen(onNavigateUp: () -> Unit) { +fun ResetPasswordTokenScreen( + getState: () -> RpTokenState, setToken: (String) -> Boolean, getIntent: () -> Intent?, + clearToken: () -> Boolean, onNavigateUp: () -> Unit +) { val context = LocalContext.current var token by remember { mutableStateOf("") } - val tokenByteArray = token.toByteArray() - val focusMgr = LocalFocusManager.current + var state by remember { mutableStateOf(getState()) } + val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + context.popToast(R.string.token_activated) + state = getState() + } + } MyScaffold(R.string.reset_password_token, onNavigateUp) { OutlinedTextField( - value = token, onValueChange = { token = it }, + token, { token = it }, Modifier.fillMaxWidth(), label = { Text(stringResource(R.string.token)) }, - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - supportingText = { - AnimatedVisibility(tokenByteArray.size < 32) { - Text(stringResource(R.string.token_must_longer_than_32_byte)) - } - }, - modifier = Modifier.fillMaxWidth() + supportingText = { Text("${token.length}/32") } ) Button( onClick = { - try { - context.showOperationResultToast(Privilege.DPM.setResetPasswordToken(Privilege.DAR, tokenByteArray)) - } catch(_:SecurityException) { - context.popToast(R.string.security_exception) - } + val result = setToken(token) + context.showOperationResultToast(result) + if (result) state = getState() }, modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp), - enabled = tokenByteArray.size >= 32 + enabled = token.length >= 32 ) { Text(stringResource(R.string.set)) } - Row( - horizontalArrangement = Arrangement.SpaceBetween, + if (state.set && !state.active) Button( + onClick = { + getIntent()?.let { launcher.launch(it) } + }, modifier = Modifier.fillMaxWidth() ) { - Button( - onClick = { - if(!Privilege.DPM.isResetPasswordTokenActive(Privilege.DAR)) { - try { activateToken(context) } - catch(_:NullPointerException) { context.popToast(R.string.please_set_a_token) } - } else { context.popToast(R.string.token_already_activated) } - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.activate)) - } - Button( - onClick = { context.showOperationResultToast(Privilege.DPM.clearResetPasswordToken(Privilege.DAR)) }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.clear)) - } + Text(stringResource(R.string.activate)) + } + if (state.set) Button( + onClick = { + val result = clearToken() + context.showOperationResultToast(result) + state = getState() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.clear)) } Spacer(Modifier.padding(vertical = 5.dp)) Notes(R.string.activate_token_not_required_when_no_password) @@ -300,147 +289,81 @@ fun ResetPasswordTokenScreen(onNavigateUp: () -> Unit) { @Serializable object ResetPassword @Composable -fun ResetPasswordScreen(onNavigateUp: () -> Unit) { +fun ResetPasswordScreen(resetPassword: (String, String, Int) -> Boolean, onNavigateUp: () -> Unit) { val context = LocalContext.current - val focusMgr = LocalFocusManager.current var password by remember { mutableStateOf("") } - var useToken by remember { mutableStateOf(false) } var token by remember { mutableStateOf("") } - val tokenByteArray = token.toByteArray() - var flag by remember { mutableIntStateOf(0) } - var confirmDialog by remember { mutableStateOf(false) } + var flags by remember { mutableIntStateOf(0) } + var confirmPassword by remember { mutableStateOf("") } MyScaffold(R.string.reset_password, onNavigateUp) { - if(VERSION.SDK_INT >= 26) { + if (VERSION.SDK_INT >= 26) { OutlinedTextField( - value = token, onValueChange = { token = it }, + token, { token = it }, Modifier.fillMaxWidth().padding(bottom = 5.dp), label = { Text(stringResource(R.string.token)) }, - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - modifier = Modifier.fillMaxWidth() + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next) ) } OutlinedTextField( - value = password, - onValueChange = { password = it }, + password, { password = it }, Modifier.fillMaxWidth(), label = { Text(stringResource(R.string.password)) }, + isError = password.length in 1..3, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Next), + visualTransformation = PasswordVisualTransformation() + ) + OutlinedTextField( + confirmPassword, { confirmPassword = it }, Modifier.fillMaxWidth(), + label = { Text(stringResource(R.string.confirm_password)) }, + isError = confirmPassword != password, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), - supportingText = { Text(stringResource(R.string.reset_pwd_desc)) }, - visualTransformation = PasswordVisualTransformation(), - modifier = Modifier.fillMaxWidth() + visualTransformation = PasswordVisualTransformation() ) Spacer(Modifier.padding(vertical = 5.dp)) if(VERSION.SDK_INT >= 23) { CheckBoxItem( R.string.do_not_ask_credentials_on_boot, - flag and RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT != 0 - ) { flag = flag xor RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT } + flags and RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT != 0 + ) { flags = flags xor RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT } } CheckBoxItem( R.string.reset_password_require_entry, - flag and RESET_PASSWORD_REQUIRE_ENTRY != 0 - ) { flag = flag xor RESET_PASSWORD_REQUIRE_ENTRY } + flags and RESET_PASSWORD_REQUIRE_ENTRY != 0 + ) { flags = flags xor RESET_PASSWORD_REQUIRE_ENTRY } Spacer(Modifier.padding(vertical = 5.dp)) - if(VERSION.SDK_INT >= 26) { - Button( - onClick = { - useToken = true - confirmDialog = true - focusMgr.clearFocus() - }, - colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), - enabled = tokenByteArray.size >=32 && password.length !in 1..3, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.reset_password_with_token)) - } - } - if(VERSION.SDK_INT <= 30) { - Button( - onClick = { - useToken = false - confirmDialog = true - focusMgr.clearFocus() - }, - colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), - enabled = password.length !in 1..3, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.reset_password)) - } + Button( + onClick = { + context.showOperationResultToast(resetPassword(password, token, flags)) + }, + colors = ButtonDefaults.buttonColors(colorScheme.error, colorScheme.onError), + modifier = Modifier.fillMaxWidth(), + enabled = password == confirmPassword + ) { + Text(stringResource(R.string.reset_password)) } Notes(R.string.info_reset_password) } - if(confirmDialog) { - var confirmPassword by remember { mutableStateOf("") } - AlertDialog( - onDismissRequest = { confirmDialog = false }, - title = { Text(stringResource(R.string.reset_password)) }, - text = { - val dialogFocusMgr = LocalFocusManager.current - OutlinedTextField( - value = confirmPassword, - onValueChange = { confirmPassword = it }, - label = { Text(stringResource(R.string.confirm_password)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { dialogFocusMgr.clearFocus() }), - visualTransformation = PasswordVisualTransformation(), - modifier = Modifier.fillMaxWidth() - ) - }, - confirmButton = { - TextButton( - onClick = { - val success = if(VERSION.SDK_INT >= 26 && useToken) { - Privilege.DPM.resetPasswordWithToken(Privilege.DAR, password, tokenByteArray, flag) - } else { - Privilege.DPM.resetPassword(password, flag) - } - context.showOperationResultToast(success) - password = "" - confirmDialog = false - }, - colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error), - enabled = confirmPassword == password - ) { - Text(stringResource(R.string.confirm)) - } - }, - dismissButton = { - TextButton(onClick = { confirmDialog = false }) { - Text(stringResource(R.string.cancel)) - } - } - ) - } } @Serializable object RequiredPasswordComplexity @RequiresApi(31) @Composable -fun RequiredPasswordComplexityScreen(onNavigateUp: () -> Unit) { +fun RequiredPasswordComplexityScreen( + getComplexity: () -> PasswordComplexity, setComplexity: (PasswordComplexity) -> Unit, + onNavigateUp: () -> Unit +) { val context = LocalContext.current - val passwordComplexity = mapOf( - 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 = Privilege.DPM.requiredPasswordComplexity } + var complexity by remember { mutableStateOf(PasswordComplexity.None) } + LaunchedEffect(Unit) { complexity = getComplexity() } MyScaffold(R.string.required_password_complexity, onNavigateUp, 0.dp) { - passwordComplexity.forEach { - FullWidthRadioButtonItem(it.value, selectedItem == it.key) { selectedItem = it.key } + PasswordComplexity.entries.forEach { + FullWidthRadioButtonItem(it.text, complexity == it) { complexity = it } } - Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { - Privilege.DPM.requiredPasswordComplexity = selectedItem - selectedItem = Privilege.DPM.requiredPasswordComplexity + setComplexity(complexity) context.showOperationResultToast(true) }, - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = HorizontalPadding) + modifier = Modifier.fillMaxWidth().padding(HorizontalPadding, 8.dp) ) { Text(text = stringResource(R.string.apply)) } @@ -448,58 +371,63 @@ fun RequiredPasswordComplexityScreen(onNavigateUp: () -> Unit) { } } +data class KeyguardDisabledFeature(val id: Int, val text: Int, val requiresApi: Int = 0) +@Suppress("InlinedApi") +val keyguardDisabledFeatures = listOf( + KeyguardDisabledFeature(DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL, R.string.disable_keyguard_features_widgets), + KeyguardDisabledFeature(DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA, R.string.disable_keyguard_features_camera), + KeyguardDisabledFeature(DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS, R.string.disable_keyguard_features_notification), + KeyguardDisabledFeature(DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS, R.string.disable_keyguard_features_unredacted_notification), + KeyguardDisabledFeature(DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS, R.string.disable_keyguard_features_trust_agents), + KeyguardDisabledFeature(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, R.string.disable_keyguard_features_fingerprint), + KeyguardDisabledFeature(DevicePolicyManager.KEYGUARD_DISABLE_FACE, R.string.disable_keyguard_features_face, 28), + KeyguardDisabledFeature(DevicePolicyManager.KEYGUARD_DISABLE_IRIS, R.string.disable_keyguard_features_iris, 28), + KeyguardDisabledFeature(DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS, R.string.disable_keyguard_features_biometrics, 28), + KeyguardDisabledFeature(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL, R.string.disable_keyguard_features_shortcuts, 34) +).filter { VERSION.SDK_INT >= it.requiresApi } + +enum class KeyguardDisableMode(val text: Int) { + None(R.string.enable_all), Custom(R.string.custom), All(R.string.disable_all) +} + +data class KeyguardDisableConfig(val mode: KeyguardDisableMode, val flags: Int) + + @Serializable object KeyguardDisabledFeatures @Composable -fun KeyguardDisabledFeaturesScreen(onNavigateUp: () -> Unit) { +fun KeyguardDisabledFeaturesScreen( + getConfig: () -> KeyguardDisableConfig, setConfig: (KeyguardDisableConfig) -> Unit, + onNavigateUp: () -> Unit +) { val context = LocalContext.current - var flag by remember { mutableIntStateOf(0) } - var mode by remember { mutableIntStateOf(0) } // 0:Enable all, 1:Disable all, 2:Custom - val flagsLiat = mutableListOf( - R.string.disable_keyguard_features_widgets to KEYGUARD_DISABLE_WIDGETS_ALL, - R.string.disable_keyguard_features_camera to KEYGUARD_DISABLE_SECURE_CAMERA, - R.string.disable_keyguard_features_notification to KEYGUARD_DISABLE_SECURE_NOTIFICATIONS, - R.string.disable_keyguard_features_unredacted_notification to KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS, - R.string.disable_keyguard_features_trust_agents to KEYGUARD_DISABLE_TRUST_AGENTS, - R.string.disable_keyguard_features_fingerprint to KEYGUARD_DISABLE_FINGERPRINT - ) - if(VERSION.SDK_INT >= 28) { - flagsLiat +=R.string.disable_keyguard_features_face to KEYGUARD_DISABLE_FACE - flagsLiat += R.string.disable_keyguard_features_iris to KEYGUARD_DISABLE_IRIS - flagsLiat += R.string.disable_keyguard_features_biometrics to KEYGUARD_DISABLE_BIOMETRICS + var mode by remember { mutableStateOf(KeyguardDisableMode.None) } + var flags by remember { mutableIntStateOf(0) } + LaunchedEffect(Unit) { + val config = getConfig() + mode = config.mode + flags = config.flags } - if(VERSION.SDK_INT >= 34) flagsLiat += R.string.disable_keyguard_features_shortcuts to KEYGUARD_DISABLE_SHORTCUTS_ALL - fun refresh() { - flag = Privilege.DPM.getKeyguardDisabledFeatures(Privilege.DAR) - mode = when(flag) { - KEYGUARD_DISABLE_FEATURES_NONE -> 0 - KEYGUARD_DISABLE_FEATURES_ALL -> 1 - else -> 2 - } - } - LaunchedEffect(mode) { if(mode != 2) flag = Privilege.DPM.getKeyguardDisabledFeatures(Privilege.DAR) } - LaunchedEffect(Unit) { refresh() } MyScaffold(R.string.disable_keyguard_features, 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) { + KeyguardDisableMode.entries.forEach { + FullWidthRadioButtonItem(it.text, mode == it) { mode = it } + } + Spacer(Modifier.height(8.dp)) + AnimatedVisibility(mode == KeyguardDisableMode.Custom) { Column { - flagsLiat.forEach { - FullWidthCheckBoxItem(it.first, flag and it.second == it.second) { checked -> - flag = if(checked) flag or it.second else flag and (flag xor it.second) + keyguardDisabledFeatures.forEach { + FullWidthCheckBoxItem(it.text, flags and it.id == it.id) { checked -> + flags = flags xor it.id } } } } Button( onClick = { - val disabledFeatures = if(mode == 0) KEYGUARD_DISABLE_FEATURES_NONE else if(mode == 1) KEYGUARD_DISABLE_FEATURES_ALL else flag - Privilege.DPM.setKeyguardDisabledFeatures(Privilege.DAR, disabledFeatures) - refresh() + setConfig(KeyguardDisableConfig(mode, flags)) context.showOperationResultToast(true) }, - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = HorizontalPadding) + modifier = Modifier.fillMaxWidth().padding(HorizontalPadding, 8.dp) ) { Text(text = stringResource(R.string.apply)) } @@ -538,14 +466,3 @@ fun RequiredPasswordQualityScreen(onNavigateUp: () -> Unit) { } } } - -private fun activateToken(context: Context) { - val desc = context.getString(R.string.activate_reset_password_token_here) - val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - val confirmIntent = keyguardManager.createConfirmDeviceCredentialIntent(context.getString(R.string.app_name), desc) - if (confirmIntent != null) { - startActivity(context, confirmIntent, null) - } else { - context.showOperationResultToast(false) - } -} 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 7f0aa33..7124ce5 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/System.kt @@ -146,9 +146,6 @@ fun SystemManagerScreen( val privilege by Privilege.status.collectAsStateWithLifecycle() /** 1: reboot, 2: bug report, 3: org name, 4: org id, 5: enrollment specific id*/ var dialog by remember { mutableIntStateOf(0) } - var enrollmentSpecificId by remember { - mutableStateOf(if (VERSION.SDK_INT >= 31 && (privilege.device || privilege.profile)) Privilege.DPM.enrollmentSpecificId else "") - } MyScaffold(R.string.system, onNavigateUp, 0.dp) { FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SystemOptions) } FunctionItem(R.string.keyguard, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(Keyguard) } @@ -195,7 +192,7 @@ fun SystemManagerScreen( if(VERSION.SDK_INT >= 31) { FunctionItem(R.string.org_id, icon = R.drawable.corporate_fare_fill0) { dialog = 4 } } - if(enrollmentSpecificId != "") { + if (VERSION.SDK_INT >= 31) { FunctionItem(R.string.enrollment_specific_id, icon = R.drawable.id_card_fill0) { dialog = 5 } } if(VERSION.SDK_INT >= 24 && (privilege.device || privilege.org)) { @@ -249,7 +246,10 @@ fun SystemManagerScreen( text = { val focusMgr = LocalFocusManager.current LaunchedEffect(Unit) { - if (dialog == 5 && VERSION.SDK_INT >= 31) input = vm.getEnrollmentSpecificId() + if (dialog == 5 && VERSION.SDK_INT >= 31) { + val id = vm.getEnrollmentSpecificId() + input = id.ifEmpty { context.getString(R.string.none) } + } if (dialog == 3 && VERSION.SDK_INT >= 24) input = vm.getOrgName() } Column { @@ -290,7 +290,6 @@ fun SystemManagerScreen( if (dialog == 3 && VERSION.SDK_INT >= 24) vm.setOrgName(input) if (dialog == 4 && VERSION.SDK_INT >= 31) { context.showOperationResultToast(vm.setOrgId(input)) - enrollmentSpecificId = vm.getEnrollmentSpecificId() } dialog = 0 }, diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt index e7b3dda..2fa9197 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/UserRestriction.kt @@ -114,7 +114,7 @@ fun UserRestrictionScreen( Spacer(Modifier.padding(vertical = 2.dp)) UserRestrictionCategory.entries.forEach { FunctionItem(it.title, icon = it.icon) { - onNavigate(UserRestrictionOptions(it.id)) + onNavigate(UserRestrictionOptions(it.name)) } } } diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index bbafff7..9cec37c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -493,7 +493,7 @@ Сбросить токен пароля Токен Токен должен быть длиннее 32 байт - Токен уже активирован + Тoken activated Очистить Установить Установите токен @@ -524,7 +524,6 @@ Биометрия (слабая) Сложный числовой (без повторений) Требуемое качество пароля - Активируйте токен сброса пароля здесь. @@ -631,8 +630,6 @@ Все данные этого пользователя будут стерты, но сам пользователь не будет удален. Контролировать, может ли пользователь изменять сети, настроенные администратором.\nКогда эта блокировка включена, пользователь по-прежнему может настраивать другие сети Wi-Fi и подключаться к ним, а также использовать другие возможности Wi-Fi, такие как режим модема. Указать минимальный уровень безопасности, требуемый для сетей Wi-Fi. Устройство может не подключаться к сетям, которые не соответствуют минимальному уровню безопасности. Если текущая сеть не соответствует установленному минимальному уровню безопасности, соединение будет разорвано. - В этом режиме подсистема DNS попытается установить TLS-соединение с DNS-сервером, предоставленным сетью, прежде чем пытаться разрешить имена в открытом виде. - Будет выполнена проверка соединения с DNS-сервером, чтобы убедиться в его работоспособности.\nВ случае использования VPN совместно с частным DNS-сервером, частный DNS-сервер должен быть доступен как изнутри, так и снаружи VPN. В противном случае устройство может потерять возможность разрешать имена хостов, так как системный трафик к DNS-серверу может не проходить через VPN. Настроить постоянное VPN-соединение через определенное приложение для текущего пользователя. Это соединение автоматически предоставляется и сохраняется после перезагрузки.\nВключить блокировку: Запретить сетевое подключение, когда VPN не подключен. Этот прокси-сервер является только рекомендацией, и некоторые приложения могут его игнорировать. Журналы сети содержат события поиска DNS и вызовы библиотеки connect().\nИспользование этой функции в рабочем профиле позволит получить журналы сети только в рабочем профиле.\nНа этом устройстве не должно быть неаффилированных пользователей, если оно используется владельцем устройства. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 48ea208..2f643f3 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -517,7 +517,7 @@ Parola Sıfırlama Jetonu Jeton Jeton 32 bayttan uzun olmalıdır - Jeton zaten etkinleştirildi + Token activated Temizle Ayarla Lütfen bir jeton ayarlayın @@ -548,7 +548,6 @@ Biyometrik (Zayıf) Sayısal karmaşık (tekrar eden karakterler olmadan) Gerekli Parola Kalitesi - Parola sıfırlama jetonunu burada etkinleştir. Ayarlar diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c041c00..d855c24 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -505,7 +505,8 @@ 密码重置令牌 令牌 令牌必须大于32字节 - 令牌已经激活 + 激活密码重置令牌 + 令牌已激活 清除 设置 请先设置令牌 @@ -525,7 +526,7 @@ 禁用可信代理 禁用指纹解锁 禁用人脸解锁 - 禁用虹膜解锁(?) + 禁用虹膜解锁 禁用生物识别 禁用快捷方式 未指定 @@ -536,7 +537,6 @@ 生物识别(弱) 复杂数字(无连续性) 密码质量要求 - 在这里激活密码重置令牌 设置 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index be8e8da..cf68326 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -540,8 +540,9 @@ Unified password Reset password token Token - The token must be longer than 32-byte - Token already activated + The token must be longer than 32 byte + Activate reset password token + Token activated Clear Set Please set a token @@ -572,7 +573,6 @@ Biometrics (Weak) Numeric complex (No repeating characters) Required password quality - Activate reset password token here. Settings