From 3fd00a5ce0dca4d12af3e4f7d94bb581e497f099 Mon Sep 17 00:00:00 2001 From: BinTianqi Date: Tue, 28 May 2024 20:46:17 +0800 Subject: [PATCH] optimize some functions in App manager optimize Permitted accessibility services and Permitted IME use state to store their package list instead of create global variables update related document --- Guide.md | 13 +- .../owndroid/dpm/ApplicationManage.kt | 450 ++++++++++-------- .../com/bintianqi/owndroid/ui/Components.kt | 5 +- app/src/main/res/values-zh-rCN/strings.xml | 12 +- app/src/main/res/values/strings.xml | 12 +- 5 files changed, 286 insertions(+), 206 deletions(-) diff --git a/Guide.md b/Guide.md index 6fbf55e..9ee561f 100644 --- a/Guide.md +++ b/Guide.md @@ -681,7 +681,7 @@ pm list packages 如果是Device owner,需要API30或以上,如果是Profile owner,需要API33或以上 (谷歌并没有在文档中说明Profile owner调用此功能所需的API等级,但是,[MinoriceOWO](https://github.com/MinoriceOwO)发现Profile owner在API33以下调用这个功能会导致OwnDroid崩溃[(issue #12)](https://github.com/BinTianqi/OwnDroid/issues/12)。[commit d4e8473](https://github.com/BinTianqi/OwnDroid/commit/d4e8473218a6d91bf3608133061f8e636e48cdbb)中已对API33以下的Profile owner隐藏该功能入口) -用户无法清除这些应用的存储空间和缓存,但是可以在这里清除 +用户无法清除这些应用的存储空间,也无法强制停止应用,OwnDroid中的[清除应用存储](#清除应用存储)不受影响 ### 权限管理 @@ -729,21 +729,22 @@ pm list permissions 作用未知 -### 许可的无障碍应用&输入法 +### 许可的无障碍服务&输入法 需要的权限:Device owner或Profile owner -无障碍应用:强制启用无障碍应用 +- 允许所有 +- 允许指定app -输入法:强制启用输入法,但是不强制用户使用输入法 +无论如何,系统的无障碍服务和输入法都是允许的 -### 保持卸载的应用 +### 卸载后保留的应用 需要Device owner 需要API28或以上 -作用未知 +这个列表中的应用的APK将会一直保留,即使没有任何用户安装这个应用 ### 清除应用存储 diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/ApplicationManage.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/ApplicationManage.kt index 1f987f3..d90e5bd 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/ApplicationManage.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/ApplicationManage.kt @@ -33,6 +33,7 @@ import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext @@ -57,12 +58,6 @@ import java.io.IOException import java.io.InputStream import java.util.concurrent.Executors -private var credentialList = mutableSetOf() -private var crossProfilePkg = mutableSetOf() -private var keepUninstallPkg = mutableListOf() -private var permittedIme = mutableListOf() -private var permittedAccessibility = mutableListOf() - private var dialogConfirmButtonAction = {} private var dialogDismissButtonAction = {} private var dialogGetStatus = { false } @@ -79,9 +74,9 @@ fun ApplicationManage(navCtrl:NavHostController, pkgName: MutableState, "CrossProfilePackage" to R.string.cross_profile_package, "CrossProfileWidget" to R.string.cross_profile_widget, "CredentialManagePolicy" to R.string.credential_manage_policy, - "Accessibility" to R.string.permitted_accessibility_app, + "Accessibility" to R.string.permitted_accessibility_services, "IME" to R.string.permitted_ime, - "KeepUninstalled" to R.string.keep_uninstalled_pkgs, + "KeepUninstalled" to R.string.keep_uninstalled_packages, "InstallApp" to R.string.install_app, "UninstallApp" to R.string.uninstall_app, "ClearAppData" to R.string.clear_app_storage, @@ -255,13 +250,13 @@ private fun Home( SubPageItem(R.string.credential_manage_policy, "", R.drawable.license_fill0) { navCtrl.navigate("CredentialManagePolicy") } } if(isProfileOwner(dpm)||isDeviceOwner(dpm)) { - SubPageItem(R.string.permitted_accessibility_app, "", R.drawable.settings_accessibility_fill0) { navCtrl.navigate("Accessibility") } + SubPageItem(R.string.permitted_accessibility_services, "", R.drawable.settings_accessibility_fill0) { navCtrl.navigate("Accessibility") } } if(isDeviceOwner(dpm)||isProfileOwner(dpm)) { SubPageItem(R.string.permitted_ime, "", R.drawable.keyboard_fill0) { navCtrl.navigate("IME") } } if(VERSION.SDK_INT>=28&&isDeviceOwner(dpm)) { - SubPageItem(R.string.keep_uninstalled_pkgs, "", R.drawable.delete_fill0) { navCtrl.navigate("KeepUninstalled") } + SubPageItem(R.string.keep_uninstalled_packages, "", R.drawable.delete_fill0) { navCtrl.navigate("KeepUninstalled") } } if(VERSION.SDK_INT>=28) { SubPageItem(R.string.clear_app_storage, "", R.drawable.mop_fill0) { @@ -284,13 +279,13 @@ private fun UserCtrlDisabledPkg(pkgName:String) { val context = LocalContext.current val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val receiver = ComponentName(context,Receiver::class.java) - Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { - var pkgList = dpm.getUserControlDisabledPackages(receiver) - var listText by remember{mutableStateOf("")} + val pkgList = remember { mutableStateListOf() } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { val refresh = { - pkgList = dpm.getUserControlDisabledPackages(receiver) - listText = pkgList.toText() + pkgList.clear() + dpm.getUserControlDisabledPackages(receiver).forEach { pkgList.add(it) } } + LaunchedEffect(Unit) { refresh() } var inited by remember{mutableStateOf(false)} if(!inited) { refresh();inited=true } Spacer(Modifier.padding(vertical = 10.dp)) @@ -300,19 +295,15 @@ private fun UserCtrlDisabledPkg(pkgName:String) { Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.app_list_is)) SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize()) { - Text(text = if(listText=="") { stringResource(R.string.none)}else{listText}) + Text(text = if(pkgList.isEmpty()) stringResource(R.string.none) else pkgList.toText()) } Spacer(Modifier.padding(vertical = 5.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Button( onClick = { - if(pkgName!="") { - pkgList.add(pkgName) - dpm.setUserControlDisabledPackages(receiver,pkgList) - refresh() - }else{ - Toast.makeText(context, R.string.fail, Toast.LENGTH_SHORT).show() - } + pkgList.add(pkgName) + dpm.setUserControlDisabledPackages(receiver, pkgList) + refresh() }, modifier = Modifier.fillMaxWidth(0.49F) ) { @@ -320,13 +311,9 @@ private fun UserCtrlDisabledPkg(pkgName:String) { } Button( onClick = { - val result = if(pkgName!="") { pkgList.remove(pkgName)}else{false} - if(result) { - dpm.setUserControlDisabledPackages(receiver,pkgList) - refresh() - }else{ - Toast.makeText(context, R.string.not_exist, Toast.LENGTH_SHORT).show() - } + pkgList.remove(pkgName) + dpm.setUserControlDisabledPackages(receiver,pkgList) + refresh() }, modifier = Modifier.fillMaxWidth(0.96F) ) { @@ -429,25 +416,25 @@ private fun PermissionManage(pkgName: String, navCtrl: NavHostController) { private fun CrossProfilePkg(pkgName: String) { val context = LocalContext.current val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val receiver = ComponentName(context,Receiver::class.java) + val receiver = ComponentName(context, Receiver::class.java) + val crossProfilePkg = remember { mutableStateListOf() } + val refresh = { + crossProfilePkg.clear() + dpm.getCrossProfilePackages(receiver).forEach { crossProfilePkg += it } + } + LaunchedEffect(Unit) { refresh() } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.cross_profile_package), style = typography.headlineLarge) - var list by remember{mutableStateOf("")} - val refresh = { - crossProfilePkg = dpm.getCrossProfilePackages(receiver) - list = crossProfilePkg.toText() - } - LaunchedEffect(Unit) { refresh() } Text(text = stringResource(R.string.app_list_is)) SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize()) { - Text(text = if(list=="") stringResource(R.string.none) else list) + Text(text = if(crossProfilePkg.isEmpty()) stringResource(R.string.none) else crossProfilePkg.toText()) } Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Button( onClick = { - if(pkgName!="") { crossProfilePkg.add(pkgName) } - dpm.setCrossProfilePackages(receiver, crossProfilePkg) + crossProfilePkg.add(pkgName) + dpm.setCrossProfilePackages(receiver, crossProfilePkg.toSet()) refresh() }, modifier = Modifier.fillMaxWidth(0.49F) @@ -456,8 +443,8 @@ private fun CrossProfilePkg(pkgName: String) { } Button( onClick = { - if(pkgName!="") { crossProfilePkg.remove(pkgName) } - dpm.setCrossProfilePackages(receiver, crossProfilePkg) + crossProfilePkg.remove(pkgName) + dpm.setCrossProfilePackages(receiver, crossProfilePkg.toSet()) refresh() }, modifier = Modifier.fillMaxWidth(0.96F) @@ -465,6 +452,16 @@ private fun CrossProfilePkg(pkgName: String) { Text(stringResource(R.string.remove)) } } + Button( + onClick = { + crossProfilePkg.clear() + dpm.setCrossProfilePackages(receiver, crossProfilePkg.toSet()) + refresh() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.clear_list)) + } Spacer(Modifier.padding(vertical = 30.dp)) } } @@ -474,26 +471,27 @@ private fun CrossProfileWidget(pkgName: String) { val context = LocalContext.current val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val receiver = ComponentName(context,Receiver::class.java) - Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { - var pkgList: MutableList - var list by remember{mutableStateOf("")} - val refresh = { - pkgList = dpm.getCrossProfileWidgetProviders(receiver) - list = pkgList.toText() + val pkgList = remember { mutableStateListOf() } + val refresh = { + pkgList.clear() + dpm.getCrossProfileWidgetProviders(receiver).forEach { + pkgList += it } - LaunchedEffect(Unit) { refresh()} + } + LaunchedEffect(Unit) { refresh() } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.cross_profile_widget), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.app_list_is)) SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize()) { - Text(text = if(list=="") stringResource(R.string.none) else list) + Text(text = if(pkgList.isEmpty()) stringResource(R.string.none) else pkgList.toText()) } Spacer(Modifier.padding(vertical = 5.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Button( onClick = { - if(pkgName!="") { dpm.addCrossProfileWidgetProvider(receiver,pkgName) } + if(pkgName != "") { dpm.addCrossProfileWidgetProvider(receiver, pkgName) } refresh() }, modifier = Modifier.fillMaxWidth(0.49F) @@ -502,7 +500,7 @@ private fun CrossProfileWidget(pkgName: String) { } Button( onClick = { - if(pkgName!="") { dpm.removeCrossProfileWidgetProvider(receiver,pkgName) } + if(pkgName != "") { dpm.removeCrossProfileWidgetProvider(receiver, pkgName) } refresh() }, modifier = Modifier.fillMaxWidth(0.96F) @@ -510,6 +508,16 @@ private fun CrossProfileWidget(pkgName: String) { Text(stringResource(R.string.remove)) } } + Button( + onClick = { + pkgList.forEach { + dpm.removeCrossProfileWidgetProvider(receiver, it) + } + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.clear_list)) + } Spacer(Modifier.padding(vertical = 10.dp)) } } @@ -519,17 +527,29 @@ private fun CrossProfileWidget(pkgName: String) { private fun CredentialManagePolicy(pkgName: String) { val context = LocalContext.current val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val focusMgr = LocalFocusManager.current - var policy:PackagePolicy? - var policyType by remember{mutableIntStateOf(-1)} - var credentialListText by remember{mutableStateOf("")} + var policy: PackagePolicy? + var policyType by remember{ mutableIntStateOf(-1) } + val credentialList = remember { mutableStateListOf() } val refreshPolicy = { policy = dpm.credentialManagerPolicy policyType = policy?.policyType ?: -1 - credentialList = policy?.packageNames ?: mutableSetOf() - credentialList = credentialList.toMutableSet() + (policy?.packageNames ?: mutableSetOf()).forEach { credentialList += it } } - LaunchedEffect(Unit) { refreshPolicy(); credentialListText = credentialList.toText() } + val apply = { + try { + if(policyType != -1 && credentialList.isNotEmpty()) { + dpm.credentialManagerPolicy = PackagePolicy(policyType, credentialList.toSet()) + }else{ + dpm.credentialManagerPolicy = null + } + Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + } catch(e:java.lang.IllegalArgumentException) { + Toast.makeText(context, R.string.fail, Toast.LENGTH_SHORT).show() + } finally { + refreshPolicy() + } + } + LaunchedEffect(Unit) { refreshPolicy() } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.credential_manage_policy), style = typography.headlineLarge) @@ -545,8 +565,8 @@ private fun CredentialManagePolicy(pkgName: String) { ) RadioButtonItem( stringResource(R.string.whitelist), - {policyType==PACKAGE_POLICY_ALLOWLIST}, - {policyType=PACKAGE_POLICY_ALLOWLIST} + { policyType==PACKAGE_POLICY_ALLOWLIST }, + { policyType=PACKAGE_POLICY_ALLOWLIST } ) RadioButtonItem( stringResource(R.string.whitelist_and_system_app), @@ -554,18 +574,18 @@ private fun CredentialManagePolicy(pkgName: String) { { policyType=PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM } ) Spacer(Modifier.padding(vertical = 5.dp)) - AnimatedVisibility(policyType!=-1) { + AnimatedVisibility(policyType != -1) { Column { Text(stringResource(R.string.app_list_is)) SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())) { - Text(text = if(credentialListText!="") credentialListText else stringResource(R.string.none)) + Text(text = if(credentialList.isEmpty()) stringResource(R.string.none) else credentialList.toText()) } Spacer(Modifier.padding(vertical = 10.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Button( onClick = { - if(pkgName!="") { credentialList.add(pkgName) } - credentialListText = credentialList.toText() + credentialList.add(pkgName) + apply() }, modifier = Modifier.fillMaxWidth(0.49F) ) { @@ -573,36 +593,24 @@ private fun CredentialManagePolicy(pkgName: String) { } Button( onClick = { - if(pkgName!="") { credentialList.remove(pkgName) } - credentialListText = credentialList.toText() + credentialList.remove(pkgName) + apply() }, modifier = Modifier.fillMaxWidth(0.96F) ) { Text(stringResource(R.string.remove)) } } - } - } - Button( - onClick = { - focusMgr.clearFocus() - try{ - if(policyType!=-1&&credentialList.isNotEmpty()) { - dpm.credentialManagerPolicy = PackagePolicy(policyType, credentialList) - }else{ - dpm.credentialManagerPolicy = null - } - Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - }catch(e:java.lang.IllegalArgumentException) { - Toast.makeText(context, R.string.fail, Toast.LENGTH_SHORT).show() - }finally { - refreshPolicy() - credentialListText = credentialList.toText() + Button( + onClick = { + credentialList.clear() + apply() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.clear_list)) } - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) + } } Spacer(Modifier.padding(vertical = 30.dp)) } @@ -613,51 +621,82 @@ private fun PermittedAccessibility(pkgName: String) { val context = LocalContext.current val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val receiver = ComponentName(context,Receiver::class.java) - val focusMgr = LocalFocusManager.current - Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { + val pkgList = remember { mutableStateListOf() } + var allowAll by remember { mutableStateOf(false) } + val refresh = { + pkgList.clear() + val getList = dpm.getPermittedAccessibilityServices(receiver) + if(getList != null) { + allowAll = false + getList.forEach { pkgList += it } + } else { + allowAll = true + } + } + LaunchedEffect(Unit) { refresh() } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.permitted_accessibility_app), style = typography.headlineLarge) - var listText by remember{ mutableStateOf("") } - LaunchedEffect(Unit) { - val getList = dpm.getPermittedAccessibilityServices(receiver) - if(getList!=null) { permittedAccessibility = getList } - listText = permittedAccessibility.toText() - } + Text(text = stringResource(R.string.permitted_accessibility_services), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) - Text(text = stringResource(R.string.app_list_is)) - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize()) { - Text(text = if(listText=="") stringResource(R.string.none) else listText) - } - Spacer(Modifier.padding(vertical = 5.dp)) - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { permittedAccessibility.add(pkgName); listText = permittedAccessibility.toText() }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { permittedAccessibility.remove(pkgName); listText = permittedAccessibility.toText() }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } - Button( - onClick = { - focusMgr.clearFocus() - Toast.makeText( - context, - if(dpm.setPermittedAccessibilityServices(receiver, permittedAccessibility)) R.string.success else R.string.fail , - Toast.LENGTH_SHORT - ).show() - val getList = dpm.getPermittedAccessibilityServices(receiver) - if(getList!=null) { permittedAccessibility = getList } - listText = permittedAccessibility.toText() - }, - modifier = Modifier.fillMaxWidth() + Row( + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(horizontal = 6.dp, vertical = 8.dp) ) { - Text(text = stringResource(R.string.apply)) + Text(stringResource(R.string.allow_all), style = typography.titleLarge) + Switch( + checked = allowAll, + onCheckedChange = { + dpm.setPermittedAccessibilityServices(receiver, if(it) null else listOf()) + refresh() + } + ) + } + AnimatedVisibility(!allowAll) { + Column { + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize()) { + if (pkgList.isEmpty()) { + Text(stringResource(R.string.only_system_accessibility_allowed)) + } else { + Text(stringResource(R.string.permitted_packages_is) + pkgList.toText()) + } + } + Spacer(Modifier.padding(vertical = 5.dp)) + Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) { + Button( + onClick = { + pkgList.add(pkgName) + dpm.setPermittedAccessibilityServices(receiver, pkgList) + refresh() + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.add)) + } + Button( + onClick = { + pkgList.remove(pkgName) + dpm.setPermittedAccessibilityServices(receiver, pkgList) + refresh() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.remove)) + } + } + Button( + onClick = { + pkgList.clear() + dpm.setPermittedAccessibilityServices(receiver, pkgList) + refresh() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.clear_list)) + } + } + } + Information { + Text(stringResource(R.string.system_accessibility_always_allowed)) } Spacer(Modifier.padding(vertical = 30.dp)) } @@ -668,51 +707,82 @@ private fun PermittedIME(pkgName: String) { val context = LocalContext.current val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val receiver = ComponentName(context,Receiver::class.java) - val focusMgr = LocalFocusManager.current + val permittedIme = remember { mutableStateListOf() } + var allowAll by remember { mutableStateOf(false) } + val refresh = { + permittedIme.clear() + val getList = dpm.getPermittedInputMethods(receiver) + if(getList != null) { + allowAll = false + getList.forEach { permittedIme += it } + } else { + allowAll = true + } + } + LaunchedEffect(Unit) { refresh() } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.permitted_ime), style = typography.headlineLarge) - var imeListText by remember{ mutableStateOf("") } - LaunchedEffect(Unit) { - val getList = dpm.getPermittedInputMethods(receiver) - if(getList!=null) { permittedIme = getList } - imeListText = permittedIme.toText() - } Spacer(Modifier.padding(vertical = 5.dp)) - Text(text = stringResource(R.string.app_list_is)) - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())) { - Text(text = if(imeListText=="") stringResource(R.string.none) else imeListText) - } - Spacer(Modifier.padding(vertical = 5.dp)) - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { permittedIme.add(pkgName); imeListText = permittedIme.toText() }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { permittedIme.remove(pkgName); imeListText = permittedIme.toText() }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } - Button( - onClick = { - focusMgr.clearFocus() - Toast.makeText( - context, - if(dpm.setPermittedInputMethods(receiver, permittedIme)) R.string.success else R.string.fail, - Toast.LENGTH_SHORT - ).show() - val getList = dpm.getPermittedInputMethods(receiver) - if(getList!=null) { permittedIme = getList } - imeListText = permittedIme.toText() - }, - modifier = Modifier.fillMaxWidth() + Row( + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(horizontal = 6.dp, vertical = 8.dp) ) { - Text(stringResource(R.string.apply)) + Text(stringResource(R.string.allow_all), style = typography.titleLarge) + Switch( + checked = allowAll, + onCheckedChange = { + dpm.setPermittedInputMethods(receiver, if(it) null else listOf()) + refresh() + } + ) + } + AnimatedVisibility(!allowAll) { + Column { + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())) { + if(permittedIme.isEmpty()) { + Text(stringResource(R.string.only_system_ime_allowed)) + } else { + Text(stringResource(R.string.permitted_packages_is) + permittedIme.toText()) + } + } + Spacer(Modifier.padding(vertical = 5.dp)) + Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) { + Button( + onClick = { + permittedIme.add(pkgName) + dpm.setPermittedInputMethods(receiver, permittedIme) + refresh() + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.add)) + } + Button( + onClick = { + permittedIme.remove(pkgName) + dpm.setPermittedInputMethods(receiver, permittedIme) + refresh() + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text(stringResource(R.string.remove)) + } + } + Button( + onClick = { + permittedIme.clear() + dpm.setPermittedInputMethods(receiver, permittedIme) + refresh() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.clear_list)) + } + } + } + Information { + Text(stringResource(R.string.system_ime_always_allowed)) } Spacer(Modifier.padding(vertical = 30.dp)) } @@ -724,27 +794,27 @@ private fun KeepUninstalledApp(pkgName: String) { val context = LocalContext.current val dpm = context.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val receiver = ComponentName(context,Receiver::class.java) - val focusMgr = LocalFocusManager.current + val pkgList = remember { mutableStateListOf() } + val refresh = { + pkgList.clear() + dpm.getKeepUninstalledPackages(receiver)?.forEach { pkgList += it } + } + LaunchedEffect(Unit) { refresh() } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.keep_uninstalled_pkgs), style = typography.headlineLarge) - var listText by remember{ mutableStateOf("") } - LaunchedEffect(Unit) { - val getList = dpm.getKeepUninstalledPackages(receiver) - if(getList!=null) { keepUninstallPkg = getList } - listText = keepUninstallPkg.toText() - } + Text(text = stringResource(R.string.keep_uninstalled_packages), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.app_list_is)) SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())) { - Text(text = if(listText=="") stringResource(R.string.none) else listText) + Text(text = if(pkgList.isEmpty()) stringResource(R.string.none) else pkgList.toText()) } Spacer(Modifier.padding(vertical = 5.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Button( onClick = { - keepUninstallPkg.add(pkgName) - listText = keepUninstallPkg.toText() + pkgList.add(pkgName) + dpm.setKeepUninstalledPackages(receiver, pkgList) + refresh() }, modifier = Modifier.fillMaxWidth(0.49F) ) { @@ -752,8 +822,9 @@ private fun KeepUninstalledApp(pkgName: String) { } Button( onClick = { - keepUninstallPkg.remove(pkgName) - listText = keepUninstallPkg.toText() + pkgList.remove(pkgName) + dpm.setKeepUninstalledPackages(receiver, pkgList) + refresh() }, modifier = Modifier.fillMaxWidth(0.96F) ) { @@ -762,16 +833,13 @@ private fun KeepUninstalledApp(pkgName: String) { } Button( onClick = { - focusMgr.clearFocus() - dpm.setKeepUninstalledPackages(receiver, keepUninstallPkg) - val getList = dpm.getKeepUninstalledPackages(receiver) - if(getList!=null) { keepUninstallPkg = getList } - listText = keepUninstallPkg.toText() - Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + pkgList.clear() + dpm.setKeepUninstalledPackages(receiver, pkgList) + refresh() }, modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) + ) { + Text(stringResource(R.string.clear_list)) } Spacer(Modifier.padding(vertical = 30.dp)) } diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt index d9268f5..82d349e 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt @@ -65,15 +65,14 @@ fun NavIcon(operation: () -> Unit) { @Composable fun Information(content: @Composable ()->Unit) { - Column(modifier = Modifier.fillMaxWidth().padding(start = 5.dp)) { + Column(modifier = Modifier.fillMaxWidth().padding(start = 5.dp, top = 20.dp)) { Icon( painter = painterResource(R.drawable.info_fill0), contentDescription = "info", tint = colorScheme.onBackground.copy(alpha = 0.8F) ) Spacer(Modifier.padding(vertical = 1.dp)) - Row { - Spacer(Modifier.padding(horizontal = 1.dp)) + Column(modifier = Modifier.padding(start = 2.dp)) { content() } } diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 23cc155..1758021 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -48,6 +48,7 @@ 当前状态: 开始 未知错误 + 允许全部 点击以激活 @@ -268,7 +269,7 @@ 未安装 防卸载 禁止用户控制 - 用户将无法清除应用的存储空间和缓存 + 用户将无法清除这些应用的存储空间或强制停止这些应用 应用列表: 清空列表 权限管理 @@ -276,9 +277,14 @@ 跨资料微件 凭据管理策略 白名单和系统应用 - 许可的无障碍应用 + 许可的应用:\n + 许可的无障碍服务 + 只允许系统无障碍服务 + 系统的无障碍服务不受影响 + 只允许系统输入法 + 系统输入法不受影响 许可的输入法 - 保持卸载的应用 + 卸载后保留的应用 数据清除 清除应用存储 这个应用的存储空间将被清空 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fefc33b..7e628eb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -51,6 +51,7 @@ Current status:  Start Unknown error + Allow all Click to activate @@ -283,7 +284,7 @@ Block uninstall Disable user control - If you set this, you cannot clear storage or cache of the app. + If you set this, you cannot clear these apps\' storage or force stop them. App list: Clear list Permission manage @@ -291,9 +292,14 @@ Cross profile widget Credential manage policy Whitelist and system app - Permitted accessibility app + Permitted packages: \n + Permitted accessibility services + Only system accessibility services are allowed + System accessibility services are always allowed. + Only system input methods are allowed + System input methods are always allowed. Permitted IME - Keep uninstalled packages + Keep uninstalled packages Clear data Clear app storage This app\'s storage will be cleared