ViewModel refactoring: Password part

Fix bugs (#178, #179)
This commit is contained in:
BinTianqi
2025-10-09 18:27:40 +08:00
parent 59556ae23d
commit 44aad18814
11 changed files with 310 additions and 283 deletions

View File

@@ -177,7 +177,6 @@ import com.bintianqi.owndroid.dpm.ResetPassword
import com.bintianqi.owndroid.dpm.ResetPasswordScreen import com.bintianqi.owndroid.dpm.ResetPasswordScreen
import com.bintianqi.owndroid.dpm.ResetPasswordToken import com.bintianqi.owndroid.dpm.ResetPasswordToken
import com.bintianqi.owndroid.dpm.ResetPasswordTokenScreen import com.bintianqi.owndroid.dpm.ResetPasswordTokenScreen
import com.bintianqi.owndroid.dpm.Restriction
import com.bintianqi.owndroid.dpm.SecurityLogging import com.bintianqi.owndroid.dpm.SecurityLogging
import com.bintianqi.owndroid.dpm.SecurityLoggingScreen import com.bintianqi.owndroid.dpm.SecurityLoggingScreen
import com.bintianqi.owndroid.dpm.SetDefaultDialer 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.UsersOptionsScreen
import com.bintianqi.owndroid.dpm.UsersScreen import com.bintianqi.owndroid.dpm.UsersScreen
import com.bintianqi.owndroid.dpm.WiFi 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.WifiScreen
import com.bintianqi.owndroid.dpm.WifiSecurityLevel import com.bintianqi.owndroid.dpm.WifiSecurityLevel
import com.bintianqi.owndroid.dpm.WifiSecurityLevelScreen import com.bintianqi.owndroid.dpm.WifiSecurityLevelScreen
@@ -601,12 +598,24 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
::navigateUp) ::navigateUp)
} }
composable<Password> { PasswordScreen(::navigateUp, ::navigate) } composable<Password> { PasswordScreen(vm, ::navigateUp, ::navigate) }
composable<PasswordInfo> { PasswordInfoScreen(::navigateUp) } composable<PasswordInfo> {
composable<ResetPasswordToken> { ResetPasswordTokenScreen(::navigateUp) } PasswordInfoScreen(vm::getPasswordComplexity, vm::isPasswordComplexitySufficient,
composable<ResetPassword> { ResetPasswordScreen(::navigateUp) } vm::isUsingUnifiedPassword, ::navigateUp)
composable<RequiredPasswordComplexity> { RequiredPasswordComplexityScreen(::navigateUp) } }
composable<KeyguardDisabledFeatures> { KeyguardDisabledFeaturesScreen(::navigateUp) } composable<ResetPasswordToken> {
ResetPasswordTokenScreen(vm::getRpTokenState, vm::setRpToken,
vm::createActivateRpTokenIntent, vm::clearRpToken, ::navigateUp)
}
composable<ResetPassword> { ResetPasswordScreen(vm::resetPassword, ::navigateUp) }
composable<RequiredPasswordComplexity> {
RequiredPasswordComplexityScreen(vm::getRequiredPasswordComplexity,
vm::setRequiredPasswordComplexity, ::navigateUp)
}
composable<KeyguardDisabledFeatures> {
KeyguardDisabledFeaturesScreen(vm::getKeyguardDisableConfig,
vm::setKeyguardDisableConfig, ::navigateUp)
}
composable<RequiredPasswordQuality> { RequiredPasswordQualityScreen(::navigateUp) } composable<RequiredPasswordQuality> { RequiredPasswordQualityScreen(::navigateUp) }
composable<Settings> { SettingsScreen(::navigateUp, ::navigate) } composable<Settings> { SettingsScreen(::navigateUp, ::navigate) }

View File

@@ -1,9 +1,9 @@
package com.bintianqi.owndroid package com.bintianqi.owndroid
import android.accounts.Account import android.accounts.Account
import android.annotation.SuppressLint
import android.app.ActivityOptions import android.app.ActivityOptions
import android.app.Application import android.app.Application
import android.app.KeyguardManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.admin.DeviceAdminInfo import android.app.admin.DeviceAdminInfo
import android.app.admin.DeviceAdminReceiver 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.IntentFilterDirection
import com.bintianqi.owndroid.dpm.IntentFilterOptions import com.bintianqi.owndroid.dpm.IntentFilterOptions
import com.bintianqi.owndroid.dpm.IpMode 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.NetworkStatsData
import com.bintianqi.owndroid.dpm.NetworkStatsTarget import com.bintianqi.owndroid.dpm.NetworkStatsTarget
import com.bintianqi.owndroid.dpm.PasswordComplexity
import com.bintianqi.owndroid.dpm.PendingSystemUpdateInfo import com.bintianqi.owndroid.dpm.PendingSystemUpdateInfo
import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceInfo import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceInfo
import com.bintianqi.owndroid.dpm.PrivateDnsConfiguration import com.bintianqi.owndroid.dpm.PrivateDnsConfiguration
import com.bintianqi.owndroid.dpm.PrivateDnsMode
import com.bintianqi.owndroid.dpm.ProxyMode import com.bintianqi.owndroid.dpm.ProxyMode
import com.bintianqi.owndroid.dpm.ProxyType import com.bintianqi.owndroid.dpm.ProxyType
import com.bintianqi.owndroid.dpm.QueryNetworkStatsParams import com.bintianqi.owndroid.dpm.QueryNetworkStatsParams
import com.bintianqi.owndroid.dpm.RecommendedProxyConf import com.bintianqi.owndroid.dpm.RecommendedProxyConf
import com.bintianqi.owndroid.dpm.RpTokenState
import com.bintianqi.owndroid.dpm.SsidPolicy import com.bintianqi.owndroid.dpm.SsidPolicy
import com.bintianqi.owndroid.dpm.SsidPolicyType import com.bintianqi.owndroid.dpm.SsidPolicyType
import com.bintianqi.owndroid.dpm.SystemOptionsStatus import com.bintianqi.owndroid.dpm.SystemOptionsStatus
@@ -113,7 +118,6 @@ import java.time.ZoneId
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.reflect.jvm.jvmErasure import kotlin.reflect.jvm.jvmErasure
import kotlin.system.measureTimeMillis
class MyViewModel(application: Application): AndroidViewModel(application) { class MyViewModel(application: Application): AndroidViewModel(application) {
val myRepo = getApplication<MyApplication>().myRepo val myRepo = getApplication<MyApplication>().myRepo
@@ -1472,8 +1476,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
@RequiresApi(29) @RequiresApi(29)
fun getPrivateDns(): PrivateDnsConfiguration { fun getPrivateDns(): PrivateDnsConfiguration {
val mode = DPM.getGlobalPrivateDnsMode(DAR)
return PrivateDnsConfiguration( return PrivateDnsConfiguration(
DPM.getGlobalPrivateDnsMode(DAR), DPM.getGlobalPrivateDnsHost(DAR) ?: "" PrivateDnsMode.entries.find { it.id == mode }!!, DPM.getGlobalPrivateDnsHost(DAR) ?: ""
) )
} }
@Suppress("PrivateApi") @Suppress("PrivateApi")
@@ -1483,7 +1488,8 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
val field = DevicePolicyManager::class.java.getDeclaredField("mService") val field = DevicePolicyManager::class.java.getDeclaredField("mService")
field.isAccessible = true field.isAccessible = true
val dpm = field.get(DPM) as IDevicePolicyManager 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 result == DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -1645,6 +1651,107 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
fun removeApnConfig(id: Int): Boolean { fun removeApnConfig(id: Int): Boolean {
return DPM.removeOverrideApn(DAR, id) 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( data class ThemeSettings(

View File

@@ -88,7 +88,7 @@ object UserRestrictionsRepository {
) )
fun getData(id: String): Pair<Int, List<Restriction>> { fun getData(id: String): Pair<Int, List<Restriction>> {
val category = UserRestrictionCategory.entries.find { it.id == id }!! val category = UserRestrictionCategory.valueOf(id)
return category.title to when (category) { return category.title to when (category) {
UserRestrictionCategory.Network -> network UserRestrictionCategory.Network -> network
UserRestrictionCategory.Connectivity -> connectivity UserRestrictionCategory.Connectivity -> connectivity
@@ -100,11 +100,11 @@ object UserRestrictionsRepository {
} }
} }
enum class UserRestrictionCategory(val id: String, val title: Int, val icon: Int) { enum class UserRestrictionCategory(val title: Int, val icon: Int) {
Network("network", R.string.network, R.drawable.language_fill0), Network(R.string.network, R.drawable.language_fill0),
Connectivity("connectivity", R.string.connectivity, R.drawable.devices_other_fill0), Connectivity(R.string.connectivity, R.drawable.devices_other_fill0),
Applications("applications", R.string.applications, R.drawable.apps_fill0), Applications(R.string.applications, R.drawable.apps_fill0),
Media("media", R.string.media, R.drawable.volume_up_fill0), Media(R.string.media, R.drawable.volume_up_fill0),
Users("users", R.string.users, R.drawable.manage_accounts_fill0), Users(R.string.users, R.drawable.manage_accounts_fill0),
Other("other", R.string.other, R.drawable.more_horiz_fill0) Other(R.string.other, R.drawable.more_horiz_fill0)
} }

View File

@@ -1346,12 +1346,11 @@ fun NetworkStatsViewerScreen(
@RequiresApi(29) @RequiresApi(29)
enum class PrivateDnsMode(val id: Int, val text: Int) { 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), Opportunistic(DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC, R.string.automatic),
Host(DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, R.string.enabled) 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 @Serializable object PrivateDns
@@ -1363,11 +1362,11 @@ fun PrivateDnsScreen(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
var mode by remember { mutableStateOf(PrivateDnsMode.Off) } var mode by remember { mutableStateOf(PrivateDnsMode.Opportunistic) }
var inputHost by remember { mutableStateOf("") } var inputHost by remember { mutableStateOf("") }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
val conf = getPrivateDns() val conf = getPrivateDns()
mode = PrivateDnsMode.entries.find { it.id == conf.mode } ?: PrivateDnsMode.Off mode = conf.mode
inputHost = conf.host inputHost = conf.host
} }
MyScaffold(R.string.private_dns, onNavigateUp, 0.dp) { MyScaffold(R.string.private_dns, onNavigateUp, 0.dp) {
@@ -1383,7 +1382,7 @@ fun PrivateDnsScreen(
Button( Button(
onClick = { onClick = {
focusMgr.clearFocus() focusMgr.clearFocus()
val result = setPrivateDns(PrivateDnsConfiguration(mode.id, inputHost)) val result = setPrivateDns(PrivateDnsConfiguration(mode, inputHost))
context.showOperationResultToast(result) context.showOperationResultToast(result)
}, },
modifier = Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding) modifier = Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding)

View File

@@ -1,23 +1,8 @@
package com.bintianqi.owndroid.dpm package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.KeyguardManager import android.app.Activity
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS import android.app.admin.DevicePolicyManager
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.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK 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_DO_NOT_ASK_CREDENTIALS_ON_BOOT
import android.app.admin.DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY import android.app.admin.DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.UserManager import android.os.UserManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions 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.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.HorizontalPadding import com.bintianqi.owndroid.HorizontalPadding
import com.bintianqi.owndroid.MyViewModel
import com.bintianqi.owndroid.Privilege import com.bintianqi.owndroid.Privilege
import com.bintianqi.owndroid.R import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.SP import com.bintianqi.owndroid.SP
@@ -86,13 +73,13 @@ import kotlinx.serialization.Serializable
@SuppressLint("NewApi") @SuppressLint("NewApi")
@Composable @Composable
fun PasswordScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { fun PasswordScreen(vm: MyViewModel,onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val privilege by Privilege.status.collectAsStateWithLifecycle() val privilege by Privilege.status.collectAsStateWithLifecycle()
var dialog by remember { mutableIntStateOf(0) } var dialog by remember { mutableIntStateOf(0) }
MyScaffold(R.string.password_and_keyguard, onNavigateUp, 0.dp) { MyScaffold(R.string.password_and_keyguard, onNavigateUp, 0.dp) {
FunctionItem(R.string.password_info, icon = R.drawable.info_fill0) { onNavigate(PasswordInfo) } FunctionItem(R.string.password_info, icon = R.drawable.info_fill0) { onNavigate(PasswordInfo) }
if(SP.displayDangerousFeatures) { if (SP.displayDangerousFeatures) {
if(VERSION.SDK_INT >= 26) { if(VERSION.SDK_INT >= 26) {
FunctionItem(R.string.reset_password_token, icon = R.drawable.key_vertical_fill0) { onNavigate(ResetPasswordToken) } 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) { if(dialog != 0) {
var input by remember { mutableStateOf("") } var input by remember { mutableStateOf("") }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
input = when(dialog) { input = when (dialog) {
1 -> Privilege.DPM.getMaximumTimeToLock(Privilege.DAR).toString() 1 -> vm.getMaxTimeToLock()
2 -> Privilege.DPM.getRequiredStrongAuthTimeout(Privilege.DAR).toString() 2 -> vm.getRequiredStrongAuthTimeout()
3 -> Privilege.DPM.getPasswordExpirationTimeout(Privilege.DAR).toString() 3 -> vm.getPasswordExpirationTimeout()
4 -> Privilege.DPM.getMaximumFailedPasswordsForWipe(Privilege.DAR).toString() 4 -> vm.getMaxFailedPasswordsForWipe()
5 -> Privilege.DPM.getPasswordHistoryLength(Privilege.DAR).toString() 5 -> vm.getPasswordHistoryLength()
else -> "" else -> 0
} }.toString()
} }
AlertDialog( AlertDialog(
title = { title = {
@@ -178,14 +165,15 @@ fun PasswordScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
TextButton( TextButton(
onClick = { onClick = {
when(dialog) { when(dialog) {
1 -> Privilege.DPM.setMaximumTimeToLock(Privilege.DAR, input.toLong()) 1 -> vm.setMaxTimeToLock(input.toLong())
2 -> Privilege.DPM.setRequiredStrongAuthTimeout(Privilege.DAR, input.toLong()) 2 -> vm.setRequiredStrongAuthTimeout(input.toLong())
3 -> Privilege.DPM.setPasswordExpirationTimeout(Privilege.DAR, input.toLong()) 3 -> vm.setPasswordExpirationTimeout(input.toLong())
4 -> Privilege.DPM.setMaximumFailedPasswordsForWipe(Privilege.DAR, input.toInt()) 4 -> vm.setMaxFailedPasswordsForWipe(input.toInt())
5 -> Privilege.DPM.setPasswordHistoryLength(Privilege.DAR, input.toInt()) 5 -> vm.setPasswordHistoryLength(input.toInt())
} }
dialog = 0 dialog = 0
} },
enabled = input.toLongOrNull() != null
) { ) {
Text(stringResource(R.string.apply)) 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 @Serializable object PasswordInfo
@Composable @Composable
fun PasswordInfoScreen(onNavigateUp: () -> Unit) { fun PasswordInfoScreen(
getComplexity: () -> PasswordComplexity, isSufficient: () -> Boolean, isUnified: () -> Boolean,
onNavigateUp: () -> Unit
) {
val privilege by Privilege.status.collectAsStateWithLifecycle() val privilege by Privilege.status.collectAsStateWithLifecycle()
var dialog by remember { mutableIntStateOf(0) } // 0:none, 1:password complexity var dialog by remember { mutableIntStateOf(0) } // 0:none, 1:password complexity
MyScaffold(R.string.password_info, onNavigateUp, 0.dp) { MyScaffold(R.string.password_info, onNavigateUp, 0.dp) {
if(VERSION.SDK_INT >= 29) { if (VERSION.SDK_INT >= 29) {
val text = when(Privilege.DPM.passwordComplexity) { InfoItem(R.string.current_password_complexity, getComplexity().text, true) { dialog = 1 }
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 }
} }
InfoItem(R.string.password_sufficient, Privilege.DPM.isActivePasswordSufficient.yesOrNo) InfoItem(R.string.password_sufficient, isSufficient().yesOrNo)
if(VERSION.SDK_INT >= 28 && privilege.work) { 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( if(dialog != 0) AlertDialog(
@@ -235,62 +227,59 @@ fun PasswordInfoScreen(onNavigateUp: () -> Unit) {
) )
} }
data class RpTokenState(val set: Boolean, val active: Boolean)
@Serializable object ResetPasswordToken @Serializable object ResetPasswordToken
@RequiresApi(26) @RequiresApi(26)
@Composable @Composable
fun ResetPasswordTokenScreen(onNavigateUp: () -> Unit) { fun ResetPasswordTokenScreen(
getState: () -> RpTokenState, setToken: (String) -> Boolean, getIntent: () -> Intent?,
clearToken: () -> Boolean, onNavigateUp: () -> Unit
) {
val context = LocalContext.current val context = LocalContext.current
var token by remember { mutableStateOf("") } var token by remember { mutableStateOf("") }
val tokenByteArray = token.toByteArray() var state by remember { mutableStateOf(getState()) }
val focusMgr = LocalFocusManager.current 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) { MyScaffold(R.string.reset_password_token, onNavigateUp) {
OutlinedTextField( OutlinedTextField(
value = token, onValueChange = { token = it }, token, { token = it }, Modifier.fillMaxWidth(),
label = { Text(stringResource(R.string.token)) }, label = { Text(stringResource(R.string.token)) },
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), supportingText = { Text("${token.length}/32") }
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
supportingText = {
AnimatedVisibility(tokenByteArray.size < 32) {
Text(stringResource(R.string.token_must_longer_than_32_byte))
}
},
modifier = Modifier.fillMaxWidth()
) )
Button( Button(
onClick = { onClick = {
try { val result = setToken(token)
context.showOperationResultToast(Privilege.DPM.setResetPasswordToken(Privilege.DAR, tokenByteArray)) context.showOperationResultToast(result)
} catch(_:SecurityException) { if (result) state = getState()
context.popToast(R.string.security_exception)
}
}, },
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp), modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
enabled = tokenByteArray.size >= 32 enabled = token.length >= 32
) { ) {
Text(stringResource(R.string.set)) Text(stringResource(R.string.set))
} }
Row( if (state.set && !state.active) Button(
horizontalArrangement = Arrangement.SpaceBetween, onClick = {
getIntent()?.let { launcher.launch(it) }
},
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Button( Text(stringResource(R.string.activate))
onClick = { }
if(!Privilege.DPM.isResetPasswordTokenActive(Privilege.DAR)) { if (state.set) Button(
try { activateToken(context) } onClick = {
catch(_:NullPointerException) { context.popToast(R.string.please_set_a_token) } val result = clearToken()
} else { context.popToast(R.string.token_already_activated) } context.showOperationResultToast(result)
}, state = getState()
modifier = Modifier.fillMaxWidth(0.49F) },
) { modifier = Modifier.fillMaxWidth()
Text(stringResource(R.string.activate)) ) {
} Text(stringResource(R.string.clear))
Button(
onClick = { context.showOperationResultToast(Privilege.DPM.clearResetPasswordToken(Privilege.DAR)) },
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.clear))
}
} }
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
Notes(R.string.activate_token_not_required_when_no_password) Notes(R.string.activate_token_not_required_when_no_password)
@@ -300,147 +289,81 @@ fun ResetPasswordTokenScreen(onNavigateUp: () -> Unit) {
@Serializable object ResetPassword @Serializable object ResetPassword
@Composable @Composable
fun ResetPasswordScreen(onNavigateUp: () -> Unit) { fun ResetPasswordScreen(resetPassword: (String, String, Int) -> Boolean, onNavigateUp: () -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val focusMgr = LocalFocusManager.current
var password by remember { mutableStateOf("") } var password by remember { mutableStateOf("") }
var useToken by remember { mutableStateOf(false) }
var token by remember { mutableStateOf("") } var token by remember { mutableStateOf("") }
val tokenByteArray = token.toByteArray() var flags by remember { mutableIntStateOf(0) }
var flag by remember { mutableIntStateOf(0) } var confirmPassword by remember { mutableStateOf("") }
var confirmDialog by remember { mutableStateOf(false) }
MyScaffold(R.string.reset_password, onNavigateUp) { MyScaffold(R.string.reset_password, onNavigateUp) {
if(VERSION.SDK_INT >= 26) { if (VERSION.SDK_INT >= 26) {
OutlinedTextField( OutlinedTextField(
value = token, onValueChange = { token = it }, token, { token = it }, Modifier.fillMaxWidth().padding(bottom = 5.dp),
label = { Text(stringResource(R.string.token)) }, label = { Text(stringResource(R.string.token)) },
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
modifier = Modifier.fillMaxWidth()
) )
} }
OutlinedTextField( OutlinedTextField(
value = password, password, { password = it }, Modifier.fillMaxWidth(),
onValueChange = { password = it },
label = { Text(stringResource(R.string.password)) }, 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), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), visualTransformation = PasswordVisualTransformation()
supportingText = { Text(stringResource(R.string.reset_pwd_desc)) },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth()
) )
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT >= 23) { if(VERSION.SDK_INT >= 23) {
CheckBoxItem( CheckBoxItem(
R.string.do_not_ask_credentials_on_boot, R.string.do_not_ask_credentials_on_boot,
flag and RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT != 0 flags and RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT != 0
) { flag = flag xor RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT } ) { flags = flags xor RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT }
} }
CheckBoxItem( CheckBoxItem(
R.string.reset_password_require_entry, R.string.reset_password_require_entry,
flag and RESET_PASSWORD_REQUIRE_ENTRY != 0 flags and RESET_PASSWORD_REQUIRE_ENTRY != 0
) { flag = flag xor RESET_PASSWORD_REQUIRE_ENTRY } ) { flags = flags xor RESET_PASSWORD_REQUIRE_ENTRY }
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT >= 26) { Button(
Button( onClick = {
onClick = { context.showOperationResultToast(resetPassword(password, token, flags))
useToken = true },
confirmDialog = true colors = ButtonDefaults.buttonColors(colorScheme.error, colorScheme.onError),
focusMgr.clearFocus() modifier = Modifier.fillMaxWidth(),
}, enabled = password == confirmPassword
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), ) {
enabled = tokenByteArray.size >=32 && password.length !in 1..3, Text(stringResource(R.string.reset_password))
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))
}
} }
Notes(R.string.info_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 @Serializable object RequiredPasswordComplexity
@RequiresApi(31) @RequiresApi(31)
@Composable @Composable
fun RequiredPasswordComplexityScreen(onNavigateUp: () -> Unit) { fun RequiredPasswordComplexityScreen(
getComplexity: () -> PasswordComplexity, setComplexity: (PasswordComplexity) -> Unit,
onNavigateUp: () -> Unit
) {
val context = LocalContext.current val context = LocalContext.current
val passwordComplexity = mapOf( var complexity by remember { mutableStateOf(PasswordComplexity.None) }
PASSWORD_COMPLEXITY_NONE to R.string.none, LaunchedEffect(Unit) { complexity = getComplexity() }
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 }
MyScaffold(R.string.required_password_complexity, onNavigateUp, 0.dp) { MyScaffold(R.string.required_password_complexity, onNavigateUp, 0.dp) {
passwordComplexity.forEach { PasswordComplexity.entries.forEach {
FullWidthRadioButtonItem(it.value, selectedItem == it.key) { selectedItem = it.key } FullWidthRadioButtonItem(it.text, complexity == it) { complexity = it }
} }
Spacer(Modifier.padding(vertical = 5.dp))
Button( Button(
onClick = { onClick = {
Privilege.DPM.requiredPasswordComplexity = selectedItem setComplexity(complexity)
selectedItem = Privilege.DPM.requiredPasswordComplexity
context.showOperationResultToast(true) 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)) 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 @Serializable object KeyguardDisabledFeatures
@Composable @Composable
fun KeyguardDisabledFeaturesScreen(onNavigateUp: () -> Unit) { fun KeyguardDisabledFeaturesScreen(
getConfig: () -> KeyguardDisableConfig, setConfig: (KeyguardDisableConfig) -> Unit,
onNavigateUp: () -> Unit
) {
val context = LocalContext.current val context = LocalContext.current
var flag by remember { mutableIntStateOf(0) } var mode by remember { mutableStateOf(KeyguardDisableMode.None) }
var mode by remember { mutableIntStateOf(0) } // 0:Enable all, 1:Disable all, 2:Custom var flags by remember { mutableIntStateOf(0) }
val flagsLiat = mutableListOf( LaunchedEffect(Unit) {
R.string.disable_keyguard_features_widgets to KEYGUARD_DISABLE_WIDGETS_ALL, val config = getConfig()
R.string.disable_keyguard_features_camera to KEYGUARD_DISABLE_SECURE_CAMERA, mode = config.mode
R.string.disable_keyguard_features_notification to KEYGUARD_DISABLE_SECURE_NOTIFICATIONS, flags = config.flags
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
} }
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) { MyScaffold(R.string.disable_keyguard_features, onNavigateUp) {
FullWidthRadioButtonItem(R.string.enable_all, mode == 0) { mode = 0 } KeyguardDisableMode.entries.forEach {
FullWidthRadioButtonItem(R.string.disable_all, mode == 1) { mode = 1 } FullWidthRadioButtonItem(it.text, mode == it) { mode = it }
FullWidthRadioButtonItem(R.string.custom, mode == 2) { mode = 2 } }
AnimatedVisibility(mode == 2) { Spacer(Modifier.height(8.dp))
AnimatedVisibility(mode == KeyguardDisableMode.Custom) {
Column { Column {
flagsLiat.forEach { keyguardDisabledFeatures.forEach {
FullWidthCheckBoxItem(it.first, flag and it.second == it.second) { checked -> FullWidthCheckBoxItem(it.text, flags and it.id == it.id) { checked ->
flag = if(checked) flag or it.second else flag and (flag xor it.second) flags = flags xor it.id
} }
} }
} }
} }
Button( Button(
onClick = { onClick = {
val disabledFeatures = if(mode == 0) KEYGUARD_DISABLE_FEATURES_NONE else if(mode == 1) KEYGUARD_DISABLE_FEATURES_ALL else flag setConfig(KeyguardDisableConfig(mode, flags))
Privilege.DPM.setKeyguardDisabledFeatures(Privilege.DAR, disabledFeatures)
refresh()
context.showOperationResultToast(true) 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)) 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)
}
}

View File

@@ -146,9 +146,6 @@ fun SystemManagerScreen(
val privilege by Privilege.status.collectAsStateWithLifecycle() val privilege by Privilege.status.collectAsStateWithLifecycle()
/** 1: reboot, 2: bug report, 3: org name, 4: org id, 5: enrollment specific id*/ /** 1: reboot, 2: bug report, 3: org name, 4: org id, 5: enrollment specific id*/
var dialog by remember { mutableIntStateOf(0) } 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) { MyScaffold(R.string.system, onNavigateUp, 0.dp) {
FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SystemOptions) } FunctionItem(R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SystemOptions) }
FunctionItem(R.string.keyguard, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(Keyguard) } FunctionItem(R.string.keyguard, icon = R.drawable.screen_lock_portrait_fill0) { onNavigate(Keyguard) }
@@ -195,7 +192,7 @@ fun SystemManagerScreen(
if(VERSION.SDK_INT >= 31) { if(VERSION.SDK_INT >= 31) {
FunctionItem(R.string.org_id, icon = R.drawable.corporate_fare_fill0) { dialog = 4 } 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 } FunctionItem(R.string.enrollment_specific_id, icon = R.drawable.id_card_fill0) { dialog = 5 }
} }
if(VERSION.SDK_INT >= 24 && (privilege.device || privilege.org)) { if(VERSION.SDK_INT >= 24 && (privilege.device || privilege.org)) {
@@ -249,7 +246,10 @@ fun SystemManagerScreen(
text = { text = {
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
LaunchedEffect(Unit) { 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() if (dialog == 3 && VERSION.SDK_INT >= 24) input = vm.getOrgName()
} }
Column { Column {
@@ -290,7 +290,6 @@ fun SystemManagerScreen(
if (dialog == 3 && VERSION.SDK_INT >= 24) vm.setOrgName(input) if (dialog == 3 && VERSION.SDK_INT >= 24) vm.setOrgName(input)
if (dialog == 4 && VERSION.SDK_INT >= 31) { if (dialog == 4 && VERSION.SDK_INT >= 31) {
context.showOperationResultToast(vm.setOrgId(input)) context.showOperationResultToast(vm.setOrgId(input))
enrollmentSpecificId = vm.getEnrollmentSpecificId()
} }
dialog = 0 dialog = 0
}, },

View File

@@ -114,7 +114,7 @@ fun UserRestrictionScreen(
Spacer(Modifier.padding(vertical = 2.dp)) Spacer(Modifier.padding(vertical = 2.dp))
UserRestrictionCategory.entries.forEach { UserRestrictionCategory.entries.forEach {
FunctionItem(it.title, icon = it.icon) { FunctionItem(it.title, icon = it.icon) {
onNavigate(UserRestrictionOptions(it.id)) onNavigate(UserRestrictionOptions(it.name))
} }
} }
} }

View File

@@ -493,7 +493,7 @@
<string name="reset_password_token">Сбросить токен пароля</string> <string name="reset_password_token">Сбросить токен пароля</string>
<string name="token">Токен</string> <string name="token">Токен</string>
<string name="token_must_longer_than_32_byte">Токен должен быть длиннее 32 байт</string> <string name="token_must_longer_than_32_byte">Токен должен быть длиннее 32 байт</string>
<string name="token_already_activated">Токен уже активирован</string> <string name="token_activated">Тoken activated</string><!--TODO-->
<string name="clear">Очистить</string> <string name="clear">Очистить</string>
<string name="set">Установить</string> <string name="set">Установить</string>
<string name="please_set_a_token">Установите токен</string> <string name="please_set_a_token">Установите токен</string>
@@ -524,7 +524,6 @@
<string name="password_quality_biometrics_weak">Биометрия (слабая)</string> <string name="password_quality_biometrics_weak">Биометрия (слабая)</string>
<string name="password_quality_numeric_complex">Сложный числовой (без повторений)</string> <string name="password_quality_numeric_complex">Сложный числовой (без повторений)</string>
<string name="required_password_quality">Требуемое качество пароля</string> <string name="required_password_quality">Требуемое качество пароля</string>
<string name="activate_reset_password_token_here">Активируйте токен сброса пароля здесь.</string>
<!--Настройки и о программе--> <!--Настройки и о программе-->
@@ -631,8 +630,6 @@
<string name="info_wipe_data_in_managed_user">Все данные этого пользователя будут стерты, но сам пользователь не будет удален.</string> <string name="info_wipe_data_in_managed_user">Все данные этого пользователя будут стерты, но сам пользователь не будет удален.</string>
<string name="info_lockdown_admin_configured_network">Контролировать, может ли пользователь изменять сети, настроенные администратором.\nКогда эта блокировка включена, пользователь по-прежнему может настраивать другие сети Wi-Fi и подключаться к ним, а также использовать другие возможности Wi-Fi, такие как режим модема.</string> <string name="info_lockdown_admin_configured_network">Контролировать, может ли пользователь изменять сети, настроенные администратором.\nКогда эта блокировка включена, пользователь по-прежнему может настраивать другие сети Wi-Fi и подключаться к ним, а также использовать другие возможности Wi-Fi, такие как режим модема.</string>
<string name="info_minimum_wifi_security_level">Указать минимальный уровень безопасности, требуемый для сетей Wi-Fi. Устройство может не подключаться к сетям, которые не соответствуют минимальному уровню безопасности. Если текущая сеть не соответствует установленному минимальному уровню безопасности, соединение будет разорвано.</string> <string name="info_minimum_wifi_security_level">Указать минимальный уровень безопасности, требуемый для сетей Wi-Fi. Устройство может не подключаться к сетям, которые не соответствуют минимальному уровню безопасности. Если текущая сеть не соответствует установленному минимальному уровню безопасности, соединение будет разорвано.</string>
<string name="info_private_dns_mode_oppertunistic">В этом режиме подсистема DNS попытается установить TLS-соединение с DNS-сервером, предоставленным сетью, прежде чем пытаться разрешить имена в открытом виде.</string>
<string name="info_set_private_dns_host">Будет выполнена проверка соединения с DNS-сервером, чтобы убедиться в его работоспособности.\nВ случае использования VPN совместно с частным DNS-сервером, частный DNS-сервер должен быть доступен как изнутри, так и снаружи VPN. В противном случае устройство может потерять возможность разрешать имена хостов, так как системный трафик к DNS-серверу может не проходить через VPN.</string>
<string name="info_always_on_vpn">Настроить постоянное VPN-соединение через определенное приложение для текущего пользователя. Это соединение автоматически предоставляется и сохраняется после перезагрузки.\nВключить блокировку: Запретить сетевое подключение, когда VPN не подключен.</string> <string name="info_always_on_vpn">Настроить постоянное VPN-соединение через определенное приложение для текущего пользователя. Это соединение автоматически предоставляется и сохраняется после перезагрузки.\nВключить блокировку: Запретить сетевое подключение, когда VPN не подключен.</string>
<string name="info_recommended_global_proxy">Этот прокси-сервер является только рекомендацией, и некоторые приложения могут его игнорировать.</string> <string name="info_recommended_global_proxy">Этот прокси-сервер является только рекомендацией, и некоторые приложения могут его игнорировать.</string>
<string name="info_network_log">Журналы сети содержат события поиска DNS и вызовы библиотеки connect().\nИспользование этой функции в рабочем профиле позволит получить журналы сети только в рабочем профиле.\nНа этом устройстве не должно быть неаффилированных пользователей, если оно используется владельцем устройства.</string> <string name="info_network_log">Журналы сети содержат события поиска DNS и вызовы библиотеки connect().\nИспользование этой функции в рабочем профиле позволит получить журналы сети только в рабочем профиле.\nНа этом устройстве не должно быть неаффилированных пользователей, если оно используется владельцем устройства.</string>

View File

@@ -517,7 +517,7 @@
<string name="reset_password_token">Parola Sıfırlama Jetonu</string> <string name="reset_password_token">Parola Sıfırlama Jetonu</string>
<string name="token">Jeton</string> <string name="token">Jeton</string>
<string name="token_must_longer_than_32_byte">Jeton 32 bayttan uzun olmalıdır</string> <string name="token_must_longer_than_32_byte">Jeton 32 bayttan uzun olmalıdır</string>
<string name="token_already_activated">Jeton zaten etkinleştirildi</string> <string name="token_activated">Token activated</string><!--TODO-->
<string name="clear">Temizle</string> <string name="clear">Temizle</string>
<string name="set">Ayarla</string> <string name="set">Ayarla</string>
<string name="please_set_a_token">Lütfen bir jeton ayarlayın</string> <string name="please_set_a_token">Lütfen bir jeton ayarlayın</string>
@@ -548,7 +548,6 @@
<string name="password_quality_biometrics_weak">Biyometrik (Zayıf)</string> <string name="password_quality_biometrics_weak">Biyometrik (Zayıf)</string>
<string name="password_quality_numeric_complex">Sayısal karmaşık (tekrar eden karakterler olmadan)</string> <string name="password_quality_numeric_complex">Sayısal karmaşık (tekrar eden karakterler olmadan)</string>
<string name="required_password_quality">Gerekli Parola Kalitesi</string> <string name="required_password_quality">Gerekli Parola Kalitesi</string>
<string name="activate_reset_password_token_here">Parola sıfırlama jetonunu burada etkinleştir.</string>
<!--Settings&About--> <!--Settings&About-->
<string name="settings">Ayarlar</string> <string name="settings">Ayarlar</string>

View File

@@ -505,7 +505,8 @@
<string name="reset_password_token">密码重置令牌</string> <string name="reset_password_token">密码重置令牌</string>
<string name="token">令牌</string> <string name="token">令牌</string>
<string name="token_must_longer_than_32_byte">令牌必须大于32字节</string> <string name="token_must_longer_than_32_byte">令牌必须大于32字节</string>
<string name="token_already_activated">令牌已经激活</string> <string name="activate_reset_password_token">激活密码重置令牌</string>
<string name="token_activated">令牌已激活</string>
<string name="clear">清除</string> <string name="clear">清除</string>
<string name="set">设置</string> <string name="set">设置</string>
<string name="please_set_a_token">请先设置令牌</string> <string name="please_set_a_token">请先设置令牌</string>
@@ -525,7 +526,7 @@
<string name="disable_keyguard_features_trust_agents">禁用可信代理</string> <string name="disable_keyguard_features_trust_agents">禁用可信代理</string>
<string name="disable_keyguard_features_fingerprint">禁用指纹解锁</string> <string name="disable_keyguard_features_fingerprint">禁用指纹解锁</string>
<string name="disable_keyguard_features_face">禁用人脸解锁</string> <string name="disable_keyguard_features_face">禁用人脸解锁</string>
<string name="disable_keyguard_features_iris">禁用虹膜解锁(?)</string> <string name="disable_keyguard_features_iris">禁用虹膜解锁</string>
<string name="disable_keyguard_features_biometrics">禁用生物识别</string> <string name="disable_keyguard_features_biometrics">禁用生物识别</string>
<string name="disable_keyguard_features_shortcuts">禁用快捷方式</string> <string name="disable_keyguard_features_shortcuts">禁用快捷方式</string>
<string name="password_quality_unspecified">未指定</string> <string name="password_quality_unspecified">未指定</string>
@@ -536,7 +537,6 @@
<string name="password_quality_biometrics_weak">生物识别(弱)</string> <string name="password_quality_biometrics_weak">生物识别(弱)</string>
<string name="password_quality_numeric_complex">复杂数字(无连续性)</string> <string name="password_quality_numeric_complex">复杂数字(无连续性)</string>
<string name="required_password_quality">密码质量要求</string> <string name="required_password_quality">密码质量要求</string>
<string name="activate_reset_password_token_here">在这里激活密码重置令牌</string>
<!--Settings&About--> <!--Settings&About-->
<string name="settings">设置</string> <string name="settings">设置</string>

View File

@@ -540,8 +540,9 @@
<string name="unified_password">Unified password</string> <string name="unified_password">Unified password</string>
<string name="reset_password_token">Reset password token</string> <string name="reset_password_token">Reset password token</string>
<string name="token">Token</string> <string name="token">Token</string>
<string name="token_must_longer_than_32_byte">The token must be longer than 32-byte</string> <string name="token_must_longer_than_32_byte">The token must be longer than 32 byte</string>
<string name="token_already_activated">Token already activated</string> <string name="activate_reset_password_token">Activate reset password token</string>
<string name="token_activated">Token activated</string>
<string name="clear">Clear</string> <string name="clear">Clear</string>
<string name="set">Set</string> <string name="set">Set</string>
<string name="please_set_a_token">Please set a token</string> <string name="please_set_a_token">Please set a token</string>
@@ -572,7 +573,6 @@
<string name="password_quality_biometrics_weak">Biometrics (Weak)</string> <string name="password_quality_biometrics_weak">Biometrics (Weak)</string>
<string name="password_quality_numeric_complex">Numeric complex (No repeating characters)</string> <string name="password_quality_numeric_complex">Numeric complex (No repeating characters)</string>
<string name="required_password_quality">Required password quality</string> <string name="required_password_quality">Required password quality</string>
<string name="activate_reset_password_token_here">Activate reset password token here. </string>
<!--Settings&About--> <!--Settings&About-->
<string name="settings">Settings</string> <string name="settings">Settings</string>