diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7a9cc92..bce0c28 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,7 +2,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.compose) - kotlin("plugin.serialization") version "2.0.0" + kotlin("plugin.serialization") version "2.0.21" } android { 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 c694454..4569bc6 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/ApplicationManage.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/ApplicationManage.kt @@ -22,7 +22,6 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -34,7 +33,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button @@ -88,6 +86,7 @@ import com.bintianqi.owndroid.getFile import com.bintianqi.owndroid.selectedPackage import com.bintianqi.owndroid.ui.Animations import com.bintianqi.owndroid.ui.Information +import com.bintianqi.owndroid.ui.ListItem import com.bintianqi.owndroid.ui.NavIcon import com.bintianqi.owndroid.ui.RadioButtonItem import com.bintianqi.owndroid.ui.SubPageItem @@ -184,8 +183,8 @@ private fun Home( val profileOwner = context.isProfileOwner var suspend by remember { mutableStateOf(false) } suspend = try{ if(VERSION.SDK_INT >= 24) dpm.isPackageSuspended(receiver, pkgName) else false } - catch(e:NameNotFoundException) { false } - catch(e:IllegalArgumentException) { false } + catch(_: NameNotFoundException) { false } + catch(_: IllegalArgumentException) { false } var hide by remember { mutableStateOf(false) } hide = dpm.isApplicationHidden(receiver, pkgName) var blockUninstall by remember { mutableStateOf(false) } @@ -202,8 +201,8 @@ private fun Home( when(appControlAction) { 1 -> { suspend = try{ if(VERSION.SDK_INT >= 24) dpm.isPackageSuspended(receiver, pkgName) else false } - catch(e:NameNotFoundException) { false } - catch(e:IllegalArgumentException) { false } + catch(_: NameNotFoundException) { false } + catch(_: IllegalArgumentException) { false } } 2 -> hide = dpm.isApplicationHidden(receiver,pkgName) 3 -> blockUninstall = dpm.isUninstallBlocked(receiver,pkgName) @@ -347,48 +346,33 @@ private fun UserCtrlDisabledPkg(pkgName:String) { Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { val refresh = { pkgList.clear() - dpm.getUserControlDisabledPackages(receiver).forEach { pkgList.add(it) } + pkgList.addAll(dpm.getUserControlDisabledPackages(receiver)) } LaunchedEffect(Unit) { refresh() } - var inited by remember{mutableStateOf(false)} - if(!inited) { refresh();inited=true } Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.ucd), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.ucd_desc)) Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.app_list_is)) - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize()) { - Text(text = if(pkgList.isEmpty()) stringResource(R.string.none) else pkgList.joinToString(separator = "\n")) + Column(modifier = Modifier.animateContentSize()) { + if(pkgList.isEmpty()) Text(stringResource(R.string.none)) + for(i in pkgList) { + ListItem(i) { pkgList -= i } + } } Spacer(Modifier.padding(vertical = 5.dp)) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - pkgList.add(pkgName) - dpm.setUserControlDisabledPackages(receiver, pkgList) - refresh() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { - pkgList.remove(pkgName) - dpm.setUserControlDisabledPackages(receiver,pkgList) - refresh() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } + Button( + onClick = { pkgList += pkgName }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.add)) } Button( - onClick = { dpm.setUserControlDisabledPackages(receiver, listOf()); refresh() }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.clear_list)) + onClick = { dpm.setUserControlDisabledPackages(receiver, pkgList); refresh() }, + modifier = Modifier.fillMaxWidth().padding(top = 8.dp) + ) { + Text(stringResource(R.string.apply)) } Spacer(Modifier.padding(vertical = 30.dp)) } @@ -503,47 +487,33 @@ private fun CrossProfilePkg(pkgName: String) { val crossProfilePkg = remember { mutableStateListOf() } val refresh = { crossProfilePkg.clear() - dpm.getCrossProfilePackages(receiver).forEach { crossProfilePkg += it } + crossProfilePkg.addAll(dpm.getCrossProfilePackages(receiver)) } 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) Text(text = stringResource(R.string.app_list_is)) - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize()) { - Text(text = if(crossProfilePkg.isEmpty()) stringResource(R.string.none) else crossProfilePkg.joinToString(separator = "\n")) - } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - crossProfilePkg.add(pkgName) - dpm.setCrossProfilePackages(receiver, crossProfilePkg.toSet()) - refresh() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { - crossProfilePkg.remove(pkgName) - dpm.setCrossProfilePackages(receiver, crossProfilePkg.toSet()) - refresh() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) + Column(modifier = Modifier.animateContentSize()) { + if(crossProfilePkg.isEmpty()) Text(stringResource(R.string.none)) + for(i in crossProfilePkg) { + ListItem(i) { crossProfilePkg -= i } } } + Button( + onClick = { crossProfilePkg += pkgName }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.add)) + } Button( onClick = { - crossProfilePkg.clear() dpm.setCrossProfilePackages(receiver, crossProfilePkg.toSet()) refresh() }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(top = 8.dp) ) { - Text(stringResource(R.string.clear_list)) + Text(stringResource(R.string.apply)) } Spacer(Modifier.padding(vertical = 30.dp)) } @@ -557,9 +527,7 @@ private fun CrossProfileWidget(pkgName: String) { val pkgList = remember { mutableStateListOf() } val refresh = { pkgList.clear() - dpm.getCrossProfileWidgetProviders(receiver).forEach { - pkgList += it - } + pkgList.addAll(dpm.getCrossProfileWidgetProviders(receiver)) } LaunchedEffect(Unit) { refresh() } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { @@ -567,39 +535,24 @@ private fun CrossProfileWidget(pkgName: String) { 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(pkgList.isEmpty()) stringResource(R.string.none) else pkgList.joinToString(separator = "\n")) + Column(modifier = Modifier.animateContentSize()) { + if(pkgList.isEmpty()) Text(stringResource(R.string.none)) + for(i in pkgList) { + ListItem(i) { + dpm.removeCrossProfileWidgetProvider(receiver, i) + refresh() + } + } } Spacer(Modifier.padding(vertical = 5.dp)) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - if(pkgName != "") { dpm.addCrossProfileWidgetProvider(receiver, pkgName) } - refresh() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { - if(pkgName != "") { dpm.removeCrossProfileWidgetProvider(receiver, pkgName) } - refresh() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } Button( onClick = { - pkgList.forEach { - dpm.removeCrossProfileWidgetProvider(receiver, it) - } + if(pkgName != "") { dpm.addCrossProfileWidgetProvider(receiver, pkgName) } + refresh() }, modifier = Modifier.fillMaxWidth() ) { - Text(stringResource(R.string.clear_list)) + Text(stringResource(R.string.add)) } Spacer(Modifier.padding(vertical = 10.dp)) } @@ -612,25 +565,12 @@ private fun CredentialManagePolicy(pkgName: String) { val dpm = context.getDPM() var policy: PackagePolicy? var policyType by remember{ mutableIntStateOf(-1) } - val credentialList = remember { mutableStateListOf() } + val pkgList = remember { mutableStateListOf() } val refreshPolicy = { policy = dpm.credentialManagerPolicy policyType = policy?.policyType ?: -1 - (policy?.packageNames ?: mutableSetOf()).forEach { credentialList += it } - } - 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.failed, Toast.LENGTH_SHORT).show() - } finally { - refreshPolicy() - } + pkgList.clear() + pkgList.addAll(policy?.packageNames ?: setOf()) } LaunchedEffect(Unit) { refreshPolicy() } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { @@ -660,38 +600,36 @@ private fun CredentialManagePolicy(pkgName: String) { AnimatedVisibility(policyType != -1) { Column { Text(stringResource(R.string.app_list_is)) - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize()) { - Text(text = if(credentialList.isEmpty()) stringResource(R.string.none) else credentialList.joinToString(separator = "\n")) - } - Spacer(Modifier.padding(vertical = 10.dp)) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - credentialList.add(pkgName) - apply() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { - credentialList.remove(pkgName) - apply() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) + Column(modifier = Modifier.animateContentSize()) { + if(pkgList.isEmpty()) Text(stringResource(R.string.none)) + for(i in pkgList) { + ListItem(i) { pkgList -= i } } } Button( - onClick = { - credentialList.clear() - apply() - }, + onClick = { pkgList += pkgName }, modifier = Modifier.fillMaxWidth() ) { - Text(stringResource(R.string.clear_list)) + Text(stringResource(R.string.add)) + } + Button( + onClick = { + try { + if(policyType != -1 && pkgList.isNotEmpty()) { + dpm.credentialManagerPolicy = PackagePolicy(policyType, pkgList.toSet()) + } else { + dpm.credentialManagerPolicy = null + } + Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + } catch(_: IllegalArgumentException) { + Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() + } finally { + refreshPolicy() + } + }, + modifier = Modifier.fillMaxWidth().padding(top = 8.dp) + ) { + Text(stringResource(R.string.apply)) } } } @@ -705,16 +643,12 @@ private fun PermittedAccessibility(pkgName: String) { val dpm = context.getDPM() val receiver = context.getReceiver() val pkgList = remember { mutableStateListOf() } - var allowAll by remember { mutableStateOf(false) } + var allowAll by remember { mutableStateOf(true) } val refresh = { pkgList.clear() val getList = dpm.getPermittedAccessibilityServices(receiver) - if(getList != null) { - allowAll = false - getList.forEach { pkgList += it } - } else { - allowAll = true - } + allowAll = getList == null + pkgList.addAll(getList ?: listOf()) } LaunchedEffect(Unit) { refresh() } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { @@ -736,45 +670,28 @@ private fun PermittedAccessibility(pkgName: String) { } 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.joinToString(separator = "\n")) + Column(modifier = Modifier.animateContentSize()) { + Text(stringResource(if(pkgList.isEmpty()) R.string.only_system_accessibility_allowed else R.string.permitted_packages_is)) + if(pkgList.isEmpty()) Text(stringResource(R.string.none)) + for(i in pkgList) { + ListItem(i) { pkgList -= i } } } 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 += pkgName }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.add)) } Button( onClick = { - pkgList.clear() dpm.setPermittedAccessibilityServices(receiver, pkgList) refresh() }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(top = 8.dp) ) { - Text(stringResource(R.string.clear_list)) + Text(stringResource(R.string.apply)) } } } @@ -790,17 +707,13 @@ private fun PermittedIME(pkgName: String) { val context = LocalContext.current val dpm = context.getDPM() val receiver = context.getReceiver() - val permittedIme = remember { mutableStateListOf() } - var allowAll by remember { mutableStateOf(false) } + val pkgList = remember { mutableStateListOf() } + var allowAll by remember { mutableStateOf(true) } val refresh = { - permittedIme.clear() + pkgList.clear() val getList = dpm.getPermittedInputMethods(receiver) - if(getList != null) { - allowAll = false - getList.forEach { permittedIme += it } - } else { - allowAll = true - } + allowAll = getList == null + pkgList.addAll(getList ?: listOf()) } LaunchedEffect(Unit) { refresh() } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { @@ -816,45 +729,27 @@ private fun PermittedIME(pkgName: String) { ) AnimatedVisibility(!allowAll) { Column { - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize()) { - if(permittedIme.isEmpty()) { - Text(stringResource(R.string.only_system_ime_allowed)) - } else { - Text(stringResource(R.string.permitted_packages_is) + permittedIme.joinToString(separator = "\n")) + Column(modifier = Modifier.animateContentSize()) { + Text(stringResource(if(pkgList.isEmpty()) R.string.only_system_ime_allowed else R.string.permitted_packages_is)) + for(i in pkgList) { + ListItem(i) { pkgList -= i } } } 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 = { pkgList += pkgName }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.add)) } Button( onClick = { - permittedIme.clear() - dpm.setPermittedInputMethods(receiver, permittedIme) + dpm.setPermittedInputMethods(receiver, pkgList) refresh() }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(top = 8.dp) ) { - Text(stringResource(R.string.clear_list)) + Text(stringResource(R.string.apply)) } } } @@ -882,41 +777,27 @@ private fun KeepUninstalledApp(pkgName: String) { 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()) { - Text(text = if(pkgList.isEmpty()) stringResource(R.string.none) else pkgList.joinToString(separator = "\n")) + Column(modifier = Modifier.animateContentSize()) { + if(pkgList.isEmpty()) Text(stringResource(R.string.none)) + for(i in pkgList) { + ListItem(i) { pkgList -= i } + } } Spacer(Modifier.padding(vertical = 5.dp)) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - pkgList.add(pkgName) - dpm.setKeepUninstalledPackages(receiver, pkgList) - refresh() - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { - pkgList.remove(pkgName) - dpm.setKeepUninstalledPackages(receiver, pkgList) - refresh() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } + Button( + onClick = { pkgList += pkgName }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.add)) } Button( onClick = { - pkgList.clear() dpm.setKeepUninstalledPackages(receiver, pkgList) refresh() }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().padding(top = 8.dp) ) { - Text(stringResource(R.string.clear_list)) + Text(stringResource(R.string.apply)) } Spacer(Modifier.padding(vertical = 30.dp)) } @@ -1075,7 +956,7 @@ private fun DefaultDialerAppDialog(status: MutableIntState, pkgName: String) { try{ dpm.setDefaultDialerApplication(pkgName) Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - }catch(e:IllegalArgumentException) { + }catch(_: IllegalArgumentException) { Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() } status.intValue = 0 @@ -1110,7 +991,7 @@ private fun EnableSystemAppDialog(status: MutableIntState, pkgName: String) { try { dpm.enableSystemApp(receiver, pkgName) Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - } catch(e: IllegalArgumentException) { + } catch(_: IllegalArgumentException) { Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show() } status.intValue = 0 diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt index 490c6b5..3c2e5b4 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/Permissions.kt @@ -73,7 +73,6 @@ fun DpmPermissions(navCtrl:NavHostController) { composable(route = "ProfileOwner") { ProfileOwner() } composable(route = "DeviceOwner") { DeviceOwner() } composable(route = "DeviceInfo") { DeviceInfo() } - composable(route = "DisableAccountManagement") { DisableAccountManagement() } composable(route = "LockScreenInfo") { LockScreenInfo() } composable(route = "SupportMsg") { SupportMsg() } composable(route = "TransformOwnership") { TransferOwnership() } @@ -135,9 +134,6 @@ private fun Home(localNavCtrl:NavHostController,listScrollState:ScrollState) { if(enrollmentSpecificId != "") { SubPageItem(R.string.enrollment_specific_id, "", R.drawable.id_card_fill0) { dialog = 1 } } - if(deviceOwner || profileOwner) { - SubPageItem(R.string.disable_account_management, "", R.drawable.account_circle_fill0) { localNavCtrl.navigate("DisableAccountManagement") } - } if(VERSION.SDK_INT >= 24 && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.device_owner_lock_screen_info, "", R.drawable.screen_lock_portrait_fill0) { localNavCtrl.navigate("LockScreenInfo") } } @@ -526,6 +522,7 @@ private fun SupportMsg() { value = shortMsg, label = { Text(stringResource(R.string.short_support_msg)) }, onValueChange = { shortMsg = it }, + minLines = 2, modifier = Modifier.fillMaxWidth().padding(bottom = 2.dp) ) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { @@ -555,6 +552,7 @@ private fun SupportMsg() { value = longMsg, label = { Text(stringResource(R.string.long_support_msg)) }, onValueChange = { longMsg = it }, + minLines = 3, modifier = Modifier.fillMaxWidth().padding(bottom = 2.dp) ) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { @@ -583,59 +581,6 @@ private fun SupportMsg() { } } -@Composable -private fun DisableAccountManagement() { - val context = LocalContext.current - val dpm = context.getDPM() - val receiver = context.getReceiver() - val focusMgr = LocalFocusManager.current - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { - Spacer(Modifier.padding(vertical = 10.dp)) - Text(text = stringResource(R.string.disable_account_management), style = typography.headlineLarge) - Text(stringResource(R.string.unknown_effect)) - var accountList by remember{ mutableStateOf("") } - val refreshList = { - val noManageAccount = dpm.accountTypesWithManagementDisabled - accountList = "" - if (noManageAccount != null) { - var count = noManageAccount.size - for(each in noManageAccount) { count -= 1; accountList += each; if(count>0) { accountList += "\n" } } - } - } - var inited by remember { mutableStateOf(false) } - if(!inited) { refreshList(); inited=true } - Spacer(Modifier.padding(vertical = 5.dp)) - Text(text = if(accountList=="") stringResource(R.string.none) else accountList) - var inputText by remember{ mutableStateOf("") } - OutlinedTextField( - value = inputText, - onValueChange = { inputText = it }, - label = { Text(stringResource(R.string.account_type)) }, - modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }) - ) - Button( - onClick={ - dpm.setAccountManagementDisabled(receiver, inputText, true) - refreshList() - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick={ - dpm.setAccountManagementDisabled(receiver, inputText, false) - refreshList() - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.remove)) - } - } -} - @SuppressLint("NewApi") @Composable private fun TransferOwnership() { diff --git a/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt b/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt index 4b546ad..49a2b7b 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/SystemManager.kt @@ -60,10 +60,13 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.OutlinedTextField @@ -114,6 +117,7 @@ import com.bintianqi.owndroid.toggle import com.bintianqi.owndroid.ui.Animations import com.bintianqi.owndroid.ui.CheckBoxItem import com.bintianqi.owndroid.ui.Information +import com.bintianqi.owndroid.ui.ListItem import com.bintianqi.owndroid.ui.RadioButtonItem import com.bintianqi.owndroid.ui.SubPageItem import com.bintianqi.owndroid.ui.SwitchItem @@ -166,6 +170,7 @@ fun SystemManage(navCtrl: NavHostController) { composable(route = "LockTaskMode") { LockTaskMode(navCtrl) } composable(route = "CaCert") { CaCert() } composable(route = "SecurityLogs") { SecurityLogs() } + composable(route = "DisableAccountManagement") { DisableAccountManagement() } composable(route = "SystemUpdatePolicy") { SysUpdatePolicy() } composable(route = "InstallSystemUpdate") { InstallSystemUpdate() } composable(route = "WipeData") { WipeData() } @@ -228,6 +233,9 @@ private fun Home(navCtrl: NavHostController, scrollState: ScrollState, rebootDia if(VERSION.SDK_INT >= 26 && !dhizuku && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.security_logs, "", R.drawable.description_fill0) { navCtrl.navigate("SecurityLogs") } } + if(deviceOwner || profileOwner) { + SubPageItem(R.string.disable_account_management, "", R.drawable.account_circle_fill0) { navCtrl.navigate("DisableAccountManagement") } + } if(VERSION.SDK_INT >= 23 && (deviceOwner || dpm.isOrgProfile(receiver))) { SubPageItem(R.string.system_update_policy, "", R.drawable.system_update_fill0) { navCtrl.navigate("SystemUpdatePolicy") } } @@ -578,7 +586,7 @@ private fun MTEPolicy() { try { dpm.mtePolicy = selectedMtePolicy Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - } catch(e:java.lang.UnsupportedOperationException) { + } catch(_: java.lang.UnsupportedOperationException) { Toast.makeText(context, R.string.unsupported, Toast.LENGTH_SHORT).show() } selectedMtePolicy = dpm.mtePolicy @@ -778,10 +786,11 @@ private fun LockTaskMode(navCtrl: NavHostController) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.lock_task_packages), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) - SelectionContainer(modifier = Modifier.animateContentSize()) { - var listText = "" - lockTaskPackages.forEach { listText += "\n" + it } - Text(text = stringResource(R.string.app_list_is) + if(listText == "") stringResource(R.string.none) else listText) + Column(modifier = Modifier.animateContentSize()) { + if(lockTaskPackages.isEmpty()) Text(text = stringResource(R.string.none)) + for(i in lockTaskPackages) { + ListItem(i) { lockTaskPackages -= i } + } } OutlinedTextField( value = inputLockTaskPkg, @@ -804,13 +813,19 @@ private fun LockTaskMode(navCtrl: NavHostController) { ) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Button( - onClick = { lockTaskPackages.add(inputLockTaskPkg) }, + onClick = { + lockTaskPackages.add(inputLockTaskPkg) + inputLockTaskPkg = "" + }, modifier = Modifier.fillMaxWidth(0.49F) ) { Text(stringResource(R.string.add)) } Button( - onClick = { lockTaskPackages.remove(inputLockTaskPkg) }, + onClick = { + lockTaskPackages.remove(inputLockTaskPkg) + inputLockTaskPkg = "" + }, modifier = Modifier.fillMaxWidth(0.96F) ) { Text(stringResource(R.string.remove)) @@ -1062,6 +1077,54 @@ private fun SecurityLogs() { } } +@Composable +private fun DisableAccountManagement() { + val context = LocalContext.current + val dpm = context.getDPM() + val receiver = context.getReceiver() + val focusMgr = LocalFocusManager.current + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)) { + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.disable_account_management), style = typography.headlineLarge) + val list = remember { mutableStateListOf() } + fun refreshList() { + list.clear() + dpm.accountTypesWithManagementDisabled?.forEach { list += it } + } + LaunchedEffect(Unit) { refreshList() } + Spacer(Modifier.padding(vertical = 5.dp)) + Column(modifier = Modifier.animateContentSize()) { + if(list.isEmpty()) Text(stringResource(R.string.none)) + for(i in list) { + ListItem(i) { + dpm.setAccountManagementDisabled(receiver, i, false) + refreshList() + } + } + } + var inputText by remember{ mutableStateOf("") } + OutlinedTextField( + value = inputText, + onValueChange = { inputText = it }, + label = { Text(stringResource(R.string.account_type)) }, + trailingIcon = { + IconButton( + onClick = { + dpm.setAccountManagementDisabled(receiver, inputText, true) + inputText = "" + refreshList() + } + ) { + Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(R.string.add)) + } + }, + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }) + ) + } +} + @SuppressLint("NewApi") @Composable fun FactoryResetProtection() { @@ -1078,7 +1141,7 @@ fun FactoryResetProtection() { var policy: FactoryResetProtectionPolicy? = FactoryResetProtectionPolicy.Builder().build() try { policy = dpm.getFactoryResetProtectionPolicy(receiver) - } catch(e: UnsupportedOperationException) { + } catch(_: UnsupportedOperationException) { unsupported = true policy = null } finally { @@ -1106,34 +1169,32 @@ fun FactoryResetProtection() { AnimatedVisibility(usePolicy) { Column { CheckBoxItem(R.string.enable_frp, enabled, { enabled = it }) - Text(stringResource(R.string.account_list_is) + "\n") - Text( - text = if(accountList.isEmpty()) stringResource(R.string.none) else accountList.joinToString(separator = "\n"), - modifier = Modifier.animateContentSize() - ) + Text(stringResource(R.string.account_list_is)) + Column(modifier = Modifier.animateContentSize()) { + if(accountList.isEmpty()) Text(stringResource(R.string.none)) + for(i in accountList) { + ListItem(i) { accountList -= i } + } + } OutlinedTextField( value = inputAccount, - label = { Text(stringResource(R.string.account)) }, onValueChange = { inputAccount = it }, + label = { Text(stringResource(R.string.account)) }, + trailingIcon = { + IconButton( + onClick = { + accountList += inputAccount + inputAccount = "" + } + ) { + Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(R.string.add)) + } + }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.padding(vertical = 2.dp)) - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { accountList.add(inputAccount) }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { accountList.remove(inputAccount) }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } } } Spacer(Modifier.padding(vertical = 5.dp)) 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 dd71f4f..3c025ea 100644 --- a/app/src/main/java/com/bintianqi/owndroid/dpm/UserManager.kt +++ b/app/src/main/java/com/bintianqi/owndroid/dpm/UserManager.kt @@ -15,6 +15,7 @@ import android.os.UserManager import android.provider.MediaStore import android.widget.Toast import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Arrangement @@ -29,10 +30,13 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.material3.Button import androidx.compose.material3.Card +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold @@ -68,6 +72,7 @@ import com.bintianqi.owndroid.toggle import com.bintianqi.owndroid.ui.Animations import com.bintianqi.owndroid.ui.CardItem import com.bintianqi.owndroid.ui.CheckBoxItem +import com.bintianqi.owndroid.ui.ListItem import com.bintianqi.owndroid.ui.SubPageItem import com.bintianqi.owndroid.ui.SwitchItem import com.bintianqi.owndroid.ui.TopBar @@ -360,57 +365,48 @@ private fun AffiliationID() { val receiver = context.getReceiver() val focusMgr = LocalFocusManager.current var input by remember { mutableStateOf("") } - val affiliationID = remember { mutableStateListOf() } - val list = affiliationID.joinToString(separator = "\n") + val list = remember { mutableStateListOf() } val refreshIds = { - affiliationID.clear() - affiliationID.addAll(dpm.getAffiliationIds(receiver)) + list.clear() + list.addAll(dpm.getAffiliationIds(receiver)) } LaunchedEffect(Unit) { refreshIds() } Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.affiliation_id), style = typography.headlineLarge) Spacer(Modifier.padding(vertical = 5.dp)) - if(list != "") { - SelectionContainer { Text(text = list) } - }else{ - Text(text = stringResource(R.string.none)) + Column(modifier = Modifier.animateContentSize()) { + if(list.isEmpty()) Text(stringResource(R.string.none)) + for(i in list) { + ListItem(i) { list -= i } + } } Spacer(Modifier.padding(vertical = 5.dp)) OutlinedTextField( value = input, - onValueChange = {input = it}, + onValueChange = { input = it }, label = { Text("ID") }, + trailingIcon = { + IconButton( + onClick = { + list += input + input = "" + } + ) { + Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(R.string.add)) + } + }, modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() }) ) Spacer(Modifier.padding(vertical = 5.dp)) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { affiliationID.add(input) }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.add)) - } - Button( - onClick = { affiliationID.remove(input) }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.remove)) - } - } Button( onClick = { - if("" in affiliationID) { - Toast.makeText(context, R.string.include_empty_string, Toast.LENGTH_SHORT).show() - } else if(affiliationID.isEmpty()) { - Toast.makeText(context, R.string.cannot_be_empty, Toast.LENGTH_SHORT).show() - } else { - dpm.setAffiliationIds(receiver, affiliationID.toSet()) - refreshIds() - Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() - } + list.removeAll(listOf("")) + dpm.setAffiliationIds(receiver, list.toSet()) + Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() + refreshIds() }, modifier = Modifier.fillMaxWidth() ) { 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 2e76b4f..136e07a 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/Components.kt @@ -3,11 +3,15 @@ package com.bintianqi.owndroid.ui import android.widget.Toast import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography @@ -240,3 +244,22 @@ fun CardItem(@StringRes title: Int, text: String) { } } } + +@Composable +fun ListItem(text: String, onDelete: () -> Unit) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp).clip(RoundedCornerShape(15)).background(colorScheme.surfaceVariant) + ) { + Text(text = text, modifier = Modifier.padding(start = 12.dp)) + IconButton( + onClick = onDelete + ) { + Icon( + painter = painterResource(R.drawable.close_fill0), + contentDescription = stringResource(R.string.delete) + ) + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 050c4c3..0591503 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -222,7 +222,7 @@ Network Wi-Fi Mac address - Min Wi-Fi security level + Minimum Wi-Fi security level Open Lockdown admin configured network WiFi SSID policy diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4940528..1cfc40c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] agp = "8.7.2" -kotlin = "2.0.0" +kotlin = "2.0.21" androidx-activity-compose = "1.9.0" navigation-compose = "2.7.7"