From 951231abe38fe349df6933d10e99a87b6b21040d Mon Sep 17 00:00:00 2001 From: BinTianqi Date: Sat, 19 Oct 2024 12:39:52 +0800 Subject: [PATCH] Use dialog to set some password options --- .../main/java/com/bintianqi/owndroid/Utils.kt | 6 +- .../com/bintianqi/owndroid/dpm/Password.kt | 274 ++++++------------ .../bintianqi/owndroid/dpm/ShizukuActivate.kt | 3 +- .../com/bintianqi/owndroid/dpm/UserManager.kt | 17 ++ app/src/main/res/values-tr/strings.xml | 4 +- app/src/main/res/values-zh-rCN/strings.xml | 10 +- app/src/main/res/values/strings.xml | 4 +- 7 files changed, 122 insertions(+), 196 deletions(-) diff --git a/app/src/main/java/com/bintianqi/owndroid/Utils.kt b/app/src/main/java/com/bintianqi/owndroid/Utils.kt index aea2e6d..dc244ca 100644 --- a/app/src/main/java/com/bintianqi/owndroid/Utils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/Utils.kt @@ -39,8 +39,8 @@ fun uriToStream( if(stream != null) { operation(stream) } stream?.close() } - catch(e: FileNotFoundException) { Toast.makeText(context, R.string.file_not_exist, Toast.LENGTH_SHORT).show() } - catch(e: IOException) { Toast.makeText(context, R.string.io_exception, Toast.LENGTH_SHORT).show() } + catch(_: FileNotFoundException) { Toast.makeText(context, R.string.file_not_exist, Toast.LENGTH_SHORT).show() } + catch(_: IOException) { Toast.makeText(context, R.string.io_exception, Toast.LENGTH_SHORT).show() } } } @@ -52,7 +52,7 @@ fun writeClipBoard(context: Context, string: String):Boolean{ val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager try { clipboardManager.setPrimaryClip(ClipData.newPlainText("", string)) - }catch(e:Exception){ + } catch(_:Exception) { return false } return true 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 43e1d84..e449a96 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Password.kt @@ -118,16 +118,12 @@ fun Password(navCtrl: NavHostController) { composable(route = "ResetPassword") { ResetPassword() } composable(route = "RequirePasswordComplexity") { PasswordComplexity() } composable(route = "DisableKeyguardFeatures") { DisableKeyguardFeatures() } - composable(route = "MaxTimeToLock") { ScreenTimeout() } - composable(route = "PasswordTimeout") { PasswordExpiration() } - composable(route = "MaxPasswordFail") { MaxFailedPasswordForWipe() } - composable(route = "RequiredStrongAuthTimeout") { RequiredStrongAuthTimeout() } - composable(route = "PasswordHistoryLength") { PasswordHistoryLength() } composable(route = "RequirePasswordQuality") { PasswordQuality() } } } } +@SuppressLint("NewApi") @Composable private fun Home(navCtrl:NavHostController, scrollState: ScrollState) { val context = LocalContext.current @@ -135,6 +131,7 @@ private fun Home(navCtrl:NavHostController, scrollState: ScrollState) { val deviceAdmin = context.isDeviceAdmin val deviceOwner = context.isDeviceOwner val profileOwner = context.isProfileOwner + var dialog by remember { mutableIntStateOf(0) } Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) { Text( text = stringResource(R.string.password_and_keyguard), @@ -157,21 +154,101 @@ private fun Home(navCtrl:NavHostController, scrollState: ScrollState) { SubPageItem(R.string.disable_keyguard_features, "", R.drawable.screen_lock_portrait_fill0) { navCtrl.navigate("DisableKeyguardFeatures") } } if(deviceOwner) { - SubPageItem(R.string.max_time_to_lock, "", R.drawable.schedule_fill0) { navCtrl.navigate("MaxTimeToLock") } - SubPageItem(R.string.pwd_expiration_timeout, "", R.drawable.lock_clock_fill0) { navCtrl.navigate("PasswordTimeout") } - SubPageItem(R.string.max_pwd_fail, "", R.drawable.no_encryption_fill0) { navCtrl.navigate("MaxPasswordFail") } - } - if(deviceAdmin){ - SubPageItem(R.string.pwd_history, "", R.drawable.history_fill0) { navCtrl.navigate("PasswordHistoryLength") } + SubPageItem(R.string.max_time_to_lock, "", R.drawable.schedule_fill0) { dialog = 1 } + SubPageItem(R.string.pwd_expiration_timeout, "", R.drawable.lock_clock_fill0) { dialog = 3 } + SubPageItem(R.string.max_pwd_fail, "", R.drawable.no_encryption_fill0) { dialog = 4 } } if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) { - SubPageItem(R.string.required_strong_auth_timeout, "", R.drawable.fingerprint_off_fill0) { navCtrl.navigate("RequiredStrongAuthTimeout") } + SubPageItem(R.string.required_strong_auth_timeout, "", R.drawable.fingerprint_off_fill0) { dialog = 2 } + } + if(deviceAdmin){ + SubPageItem(R.string.pwd_history, "", R.drawable.history_fill0) { dialog = 5 } } if(VERSION.SDK_INT < 31 && (deviceOwner || profileOwner)) { SubPageItem(R.string.required_password_quality, "", R.drawable.password_fill0) { navCtrl.navigate("RequirePasswordQuality") } } Spacer(Modifier.padding(vertical = 30.dp)) } + if(dialog != 0) { + val dpm = context.getDPM() + val receiver = context.getReceiver() + var input by remember { mutableStateOf("") } + LaunchedEffect(Unit) { + input = when(dialog) { + 1 -> dpm.getMaximumTimeToLock(receiver).toString() + 2 -> dpm.getRequiredStrongAuthTimeout(receiver).toString() + 3 -> dpm.getPasswordExpirationTimeout(receiver).toString() + 4 -> dpm.getMaximumFailedPasswordsForWipe(receiver).toString() + 5 -> dpm.getPasswordHistoryLength(receiver).toString() + else -> "" + } + } + AlertDialog( + title = { + Text(stringResource( + when(dialog) { + 1 -> R.string.max_time_to_lock + 2 -> R.string.required_strong_auth_timeout + 3 -> R.string.pwd_expiration_timeout + 4 -> R.string.max_pwd_fail + 5 -> R.string.pwd_history + else -> R.string.password + } + )) + }, + text = { + val focusMgr = LocalFocusManager.current + Column { + OutlinedTextField( + value = input, + label = { + Text(stringResource( + when(dialog) { + 1,2,3 -> R.string.time_unit_ms + 4 -> R.string.max_pwd_fail_textfield + 5 -> R.string.length + else -> R.string.password + } + )) + }, + onValueChange = { input = it }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), + modifier = Modifier.fillMaxWidth().padding(bottom = 5.dp) + ) + when(dialog) { + 4 -> Text(stringResource(R.string.max_pwd_fail_desc)) + 5 -> Text(stringResource(R.string.pwd_history_desc)) + } + Text(stringResource(R.string.zero_means_no_restriction)) + } + }, + confirmButton = { + TextButton( + onClick = { + when(dialog) { + 1 -> dpm.setMaximumTimeToLock(receiver, input.toLong()) + 2 -> dpm.setRequiredStrongAuthTimeout(receiver, input.toLong()) + 3 -> dpm.setPasswordExpirationTimeout(receiver, input.toLong()) + 4 -> dpm.setMaximumFailedPasswordsForWipe(receiver, input.toInt()) + 5 -> dpm.setPasswordHistoryLength(receiver, input.toInt()) + } + dialog = 0 + } + ) { + Text(stringResource(R.string.apply)) + } + }, + dismissButton = { + TextButton(onClick = { dialog = 0 }) { + Text(stringResource(R.string.cancel)) + } + }, + onDismissRequest = { + dialog = 0 + } + ) + } } @Composable @@ -242,7 +319,7 @@ private fun ResetPasswordToken() { if(dpm.setResetPasswordToken(receiver, tokenByteArray)) R.string.success else R.string.failed, Toast.LENGTH_SHORT ).show() - }catch(e:SecurityException) { + }catch(_:SecurityException) { Toast.makeText(context, R.string.security_exception, Toast.LENGTH_SHORT).show() } }, @@ -259,7 +336,7 @@ private fun ResetPasswordToken() { onClick = { if(!dpm.isResetPasswordTokenActive(receiver)) { try { activateToken(context) } - catch(e:NullPointerException) { Toast.makeText(context, R.string.please_set_a_token, Toast.LENGTH_SHORT).show() } + catch(_:NullPointerException) { Toast.makeText(context, R.string.please_set_a_token, Toast.LENGTH_SHORT).show() } } else { Toast.makeText(context, R.string.token_already_activated, Toast.LENGTH_SHORT).show() } }, modifier = Modifier.fillMaxWidth(0.49F) @@ -454,175 +531,6 @@ private fun PasswordComplexity() { } } -@Composable -private fun ScreenTimeout() { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - val focusMgr = LocalFocusManager.current - var inputContent by remember { mutableStateOf("") } - LaunchedEffect(Unit) { inputContent = dpm.getMaximumTimeToLock(receiver).toString() } - Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { - Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.max_time_to_lock), style = typography.headlineLarge) - Spacer(Modifier.padding(vertical = 5.dp)) - Text(text= stringResource(R.string.max_time_to_lock_desc),modifier=Modifier.padding(vertical = 2.dp)) - Spacer(Modifier.padding(vertical = 5.dp)) - OutlinedTextField( - value = inputContent, - label = { Text(stringResource(R.string.time_unit_ms)) }, - onValueChange = { inputContent = it }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), - modifier = Modifier.fillMaxWidth() - ) - Spacer(Modifier.padding(vertical = 5.dp)) - Button( - onClick = { focusMgr.clearFocus(); dpm.setMaximumTimeToLock(receiver, inputContent.toLong()) }, - modifier = Modifier.fillMaxWidth(), - enabled = inputContent != "" - ) { - Text(stringResource(R.string.apply)) - } - } -} - -@SuppressLint("NewApi") -@Composable -private fun RequiredStrongAuthTimeout() { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - val focusMgr = LocalFocusManager.current - var input by remember { mutableStateOf("") } - LaunchedEffect(Unit) { input = dpm.getRequiredStrongAuthTimeout(receiver).toString() } - Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { - Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.required_strong_auth_timeout), style = typography.headlineLarge) - Spacer(Modifier.padding(vertical = 5.dp)) - OutlinedTextField( - value = input, - label = { Text(stringResource(R.string.time_unit_ms)) }, - onValueChange = { input = it }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), - modifier = Modifier.fillMaxWidth() - ) - Spacer(Modifier.padding(vertical = 5.dp)) - Button( - onClick = { focusMgr.clearFocus(); dpm.setRequiredStrongAuthTimeout(receiver, input.toLong()) }, - modifier = Modifier.fillMaxWidth(), - enabled = input != "" - ) { - Text(stringResource(R.string.apply)) - } - Spacer(Modifier.padding(vertical = 10.dp)) - Information { Text(stringResource(R.string.zero_means_no_control)) } - Spacer(Modifier.padding(vertical = 60.dp)) - } -} - -@Composable -private fun MaxFailedPasswordForWipe() { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - val focusMgr = LocalFocusManager.current - var inputContent by remember { mutableStateOf("") } - LaunchedEffect(Unit) { inputContent = dpm.getMaximumFailedPasswordsForWipe(receiver).toString() } - Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { - Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.max_pwd_fail), style = typography.headlineLarge) - Spacer(Modifier.padding(vertical = 5.dp)) - Text(text= stringResource(R.string.max_pwd_fail_desc)) - Spacer(Modifier.padding(vertical = 5.dp)) - OutlinedTextField( - value = inputContent, - label = { Text(stringResource(R.string.max_pwd_fail_textfield)) }, - onValueChange = { inputContent = it }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), - modifier = Modifier.fillMaxWidth() - ) - Spacer(Modifier.padding(vertical = 5.dp)) - Button( - onClick = { - focusMgr.clearFocus() - dpm.setMaximumFailedPasswordsForWipe(receiver, inputContent.toInt()) - Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth(), - enabled = inputContent != "" - ) { - Text(stringResource(R.string.apply)) - } - } -} - -@Composable -private fun PasswordExpiration() { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - val focusMgr = LocalFocusManager.current - var inputContent by remember { mutableStateOf("") } - LaunchedEffect(Unit) { inputContent = dpm.getPasswordExpirationTimeout(receiver).toString() } - Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { - Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.pwd_expiration_timeout), style = typography.headlineLarge) - Spacer(Modifier.padding(vertical = 5.dp)) - OutlinedTextField( - value = inputContent, - label = { Text(stringResource(R.string.time_unit_ms)) }, - onValueChange = { inputContent = it }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), - modifier = Modifier.fillMaxWidth() - ) - Spacer(Modifier.padding(vertical = 5.dp)) - Button( - onClick = { focusMgr.clearFocus() ; dpm.setPasswordExpirationTimeout(receiver,inputContent.toLong()) }, - modifier = Modifier.fillMaxWidth(), - enabled = inputContent != "" - ) { - Text(stringResource(R.string.apply)) - } - } -} - -@Composable -private fun PasswordHistoryLength() { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - val focusMgr = LocalFocusManager.current - var inputContent by remember { mutableStateOf("") } - LaunchedEffect(Unit) { inputContent = dpm.getPasswordHistoryLength(receiver).toString() } - Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { - Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.pwd_history), style = typography.headlineLarge) - Spacer(Modifier.padding(vertical = 5.dp)) - Text(text= stringResource(R.string.pwd_history_desc)) - Spacer(Modifier.padding(vertical = 5.dp)) - OutlinedTextField( - value = inputContent, - label = { Text(stringResource(R.string.length)) }, - onValueChange = { inputContent = it }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), - modifier = Modifier.fillMaxWidth() - ) - Spacer(Modifier.padding(vertical = 5.dp)) - Button( - onClick = { focusMgr.clearFocus() ; dpm.setPasswordHistoryLength(receiver,inputContent.toInt()) }, - modifier = Modifier.fillMaxWidth(), - enabled = inputContent != "" - ) { - Text(stringResource(R.string.apply)) - } - } -} - @Composable private fun DisableKeyguardFeatures() { val context = LocalContext.current diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt index f07adaa..2b69a10 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/ShizukuActivate.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -49,7 +50,7 @@ fun ShizukuActivate() { val outputTextScrollState = rememberScrollState() var enabled by remember { mutableStateOf(false) } var bindShizuku by remember { mutableStateOf(false) } - var outputText by remember { mutableStateOf("") } + var outputText by rememberSaveable { mutableStateOf("") } var showDeviceAdminButton by remember { mutableStateOf(!context.isDeviceAdmin) } var showDeviceOwnerButton by remember { mutableStateOf(!context.isDeviceOwner) } var showOrgProfileOwnerButton by remember { mutableStateOf(true) } diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/UserManager.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/UserManager.kt index 4ead168..5b1956b 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/UserManager.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/UserManager.kt @@ -69,6 +69,7 @@ import com.bintianqi.owndroid.toggle import com.bintianqi.owndroid.ui.Animations import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.SubPageItem +import com.bintianqi.owndroid.ui.SwitchItem import com.bintianqi.owndroid.ui.TopBar import com.bintianqi.owndroid.uriToStream @@ -99,6 +100,7 @@ fun UserManage(navCtrl: NavHostController) { ) { composable(route = "Home") { Home(localNavCtrl, scrollState) } composable(route = "UserInfo") { CurrentUserInfo() } + composable(route = "Options") { Options() } composable(route = "UserOperation") { UserOperation() } composable(route = "CreateUser") { CreateUser() } composable(route = "EditUsername") { Username() } @@ -121,6 +123,9 @@ private fun Home(navCtrl: NavHostController,scrollState: ScrollState) { modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 16.dp) ) SubPageItem(R.string.user_info, "", R.drawable.person_fill0) { navCtrl.navigate("UserInfo") } + if(deviceOwner && VERSION.SDK_INT >= 28) { + SubPageItem(R.string.options, "", R.drawable.tune_fill0) { navCtrl.navigate("Options") } + } if(deviceOwner) { SubPageItem(R.string.user_operation, "", R.drawable.sync_alt_fill0) { navCtrl.navigate("UserOperation") } } @@ -144,6 +149,18 @@ private fun Home(navCtrl: NavHostController,scrollState: ScrollState) { } } +@Composable +private fun Options() { + val context = LocalContext.current + val dpm = context.getDPM() + val receiver = context.getReceiver() + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + if(VERSION.SDK_INT >= 28) { + SwitchItem(R.string.enable_logout, "", null, { dpm.isLogoutEnabled }, { dpm.setLogoutEnabled(receiver, it) }) + } + } +} + @Composable private fun CurrentUserInfo() { val context = LocalContext.current diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 7f345be..1ee51a5 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -435,6 +435,7 @@ Kullanıcı yöneticisi + Enable logout Kullanıcı adını düzenle Kullanıcı oturumunu başlat mesajı Kullanıcı oturumunu sonlandır mesajı @@ -483,8 +484,7 @@ Şifre geçerlilik süresi Ekran zaman aşımı Gereken güçlü doğrulama zaman aşımı - 0 değeri, yöneticinin zaman aşımını kontrol etmediği anlamına gelir - Kullanıcı kararına izin vermek için 0 girin, birim: milisaniye + 0 means no restriction Şifre geçmişi uzunluğu Belirtilen aralıktaki geçmiş şifreler kullanıcı tarafından ayarlanamaz Yok (Şifreye izin verilmez) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 3e5cf55..59721c9 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -29,7 +29,7 @@ 自定义 未知 重置 - 时间(ms) + 时间(毫秒) 长度 无日志 @@ -427,6 +427,7 @@ 用户管理 + 允许登出 修改用户名 用户会话开始消息 用户会话结束消息 @@ -470,13 +471,12 @@ 密码信息 留空以清除密码 最大密码错误次数 - 达到该限制会恢复出厂设置,0为无限制 + 达到该限制会恢复出厂设置 错误次数 - 密码失效超时时间 + 密码失效超时 屏幕超时 要求强验证超时 - 值为0表示OwnDroid不参与控制超时 - 超时后锁屏(毫秒),0为由用户决定 + 0表示不做限制 密码历史长度 用户无法设置指定历史范围内之前曾设置过的密码 无(允许不设密码) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f13500..a565f19 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -440,6 +440,7 @@ User manager + Enable logout Edit username Start user session message End user session message @@ -488,8 +489,7 @@ Password expiration timeout Screen timeout Required strong auth timeout - A value of 0 means the admin is not participating in controlling the timeout - Enter 0 to allow user decision, unit: millisecond + 0 means no restriction Password history length Historical passwords within the specified range cannot be set by user None (No password allowed)