diff --git a/Guide.md b/Guide.md index 5faaed6..36f1166 100644 --- a/Guide.md +++ b/Guide.md @@ -769,6 +769,8 @@ Profile owner无法禁用部分功能,工作资料中部分功能无效,wear 打开开关就是禁用对应的功能,默认情况下所有开关都是关闭的 +所有的用户限制都需要API24或以上 + ### 网络与互联网 - 配置移动数据 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 15bb4c3..fe7bc63 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "com.binbin.androidowner" minSdk = 21 targetSdk = 34 - versionCode = 18 - versionName = "4.1" + versionCode = 19 + versionName = "4.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/binbin/androidowner/MainActivity.kt b/app/src/main/java/com/binbin/androidowner/MainActivity.kt index 0bc52ce..6f57be6 100644 --- a/app/src/main/java/com/binbin/androidowner/MainActivity.kt +++ b/app/src/main/java/com/binbin/androidowner/MainActivity.kt @@ -7,27 +7,25 @@ import android.content.ComponentName import android.content.Context import android.os.Build.VERSION import android.os.Bundle -import android.os.UserManager +import android.util.DisplayMetrics import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -35,48 +33,32 @@ import androidx.core.view.WindowCompat import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable -import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.binbin.androidowner.dpm.* import com.binbin.androidowner.ui.theme.AndroidOwnerTheme -import com.binbin.androidowner.ui.theme.Animations - +import com.binbin.androidowner.ui.Animations +lateinit var displayMetrics: DisplayMetrics @ExperimentalMaterial3Api class MainActivity : ComponentActivity() { private fun registerActivityResult(){ - getUserIcon = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - userIconUri = it.data?.data - if(userIconUri==null){ Toast.makeText(applicationContext, "空URI", Toast.LENGTH_SHORT).show() } - } - getApk = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - apkUri = it.data?.data - if(apkUri==null){ Toast.makeText(applicationContext, "空URI", Toast.LENGTH_SHORT).show() } - } + getUserIcon = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { userIconUri = it.data?.data } + getApk = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { apkUri = it.data?.data } getCaCert = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { uriToStream(applicationContext,it.data?.data){stream-> caCert = stream.readBytes() if(caCert.size>50000){ Toast.makeText(applicationContext, "太大了", Toast.LENGTH_SHORT).show(); caCert = byteArrayOf() } } } - createUser = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - when(it.resultCode){ - Activity.RESULT_OK->Toast.makeText(applicationContext, "成功", Toast.LENGTH_SHORT).show() - Activity.RESULT_CANCELED->Toast.makeText(applicationContext, "用户太多了", Toast.LENGTH_SHORT).show() - UserManager.USER_CREATION_FAILED_NOT_PERMITTED->Toast.makeText(applicationContext, "不是管理员用户", Toast.LENGTH_SHORT).show() - UserManager.USER_CREATION_FAILED_NO_MORE_USERS->Toast.makeText(applicationContext, "用户太多了", Toast.LENGTH_SHORT).show() - else->Toast.makeText(applicationContext, "创建用户结果未知", Toast.LENGTH_SHORT).show() - } - } createManagedProfile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if(it.resultCode==Activity.RESULT_CANCELED){Toast.makeText(applicationContext, "用户已取消", Toast.LENGTH_SHORT).show()} } } - override fun onCreate(savedInstanceState: Bundle?) { WindowCompat.setDecorFitsSystemWindows(window, false) super.onCreate(savedInstanceState) registerActivityResult() + displayMetrics = applicationContext.resources.displayMetrics setContent { AndroidOwnerTheme { MyScaffold() @@ -88,95 +70,61 @@ class MainActivity : ComponentActivity() { @ExperimentalMaterial3Api @Composable fun MyScaffold(){ - val focusMgr = LocalFocusManager.current val navCtrl = rememberNavController() - val backStackEntry by navCtrl.currentBackStackEntryAsState() val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val topBarNameMap = mapOf( - "HomePage" to R.string.app_name, - "DeviceControl" to R.string.device_ctrl, - "Network" to R.string.network, - "ManagedProfile" to R.string.work_profile, - "Permissions" to R.string.permission, - "UserManage" to R.string.user_manage, - "ApplicationManage" to R.string.app_manage, - "UserRestriction" to R.string.user_restrict, - "Password" to R.string.password_and_keyguard, - "AppSetting" to R.string.setting, - "ShizukuActivate" to R.string.shizuku - ) - val topBarName = topBarNameMap[backStackEntry?.destination?.route]?: R.string.app_name val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - Scaffold( - topBar = { - TopAppBar( - title = {Text(text = stringResource(topBarName) , color = colorScheme.onSurface, modifier = Modifier.padding(bottom = 2.dp))}, - colors = TopAppBarDefaults.topAppBarColors( containerColor = colorScheme.surfaceVariant ), - navigationIcon = { - AnimatedVisibility( - visible = topBarName!=R.string.app_name, - enter = Animations(myContext).navIconEnterTransition, - exit = Animations(myContext).navIconExitTransition - ){ - Icon( - imageVector = Icons.AutoMirrored.Default.ArrowBack, - contentDescription = "Back", - modifier = Modifier - .padding(horizontal = 6.dp) - .clip(RoundedCornerShape(50)) - .clickable{ navCtrl.navigateUp(); focusMgr.clearFocus() } - .padding(5.dp) - ) - } - } - ) - } - ) { + NavHost( + navController = navCtrl, + startDestination = "HomePage", + modifier = Modifier + .statusBarsPadding() + .fillMaxSize() + .background(color = if(isSystemInDarkTheme()) { colorScheme.background }else{ colorScheme.primary.copy(alpha = 0.05F) }) + .imePadding(), + enterTransition = Animations().navHostEnterTransition, + exitTransition = Animations().navHostExitTransition, + popEnterTransition = Animations().navHostPopEnterTransition, + popExitTransition = Animations().navHostPopExitTransition + ){ + composable(route = "HomePage", content = { HomePage(navCtrl)}) + composable(route = "SystemManage", content = { SystemManage(navCtrl) }) + composable(route = "ManagedProfile", content = {ManagedProfile(navCtrl)}) + composable(route = "Permissions", content = { DpmPermissions(navCtrl)}) + composable(route = "ApplicationManage", content = { ApplicationManage(navCtrl)}) + composable(route = "UserRestriction", content = { UserRestriction(navCtrl)}) + composable(route = "UserManage", content = { UserManage(navCtrl)}) + composable(route = "Password", content = { Password(navCtrl)}) + composable(route = "AppSetting", content = { AppSetting(navCtrl)}) + composable(route = "Network", content = {Network(navCtrl)}) + } + LaunchedEffect(Unit){ val profileInited = sharedPref.getBoolean("ManagedProfileActivated",false) - var inited by remember{mutableStateOf(false)} - val jumpToActivateProfile = !profileInited&&isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent))) - NavHost( - navController = navCtrl, - startDestination = "HomePage", - modifier = Modifier - .fillMaxSize() - .background(color = if(isSystemInDarkTheme()) { colorScheme.background }else{ colorScheme.primary.copy(alpha = 0.05F) }) - .padding(top = it.calculateTopPadding()).imePadding(), - enterTransition = Animations(myContext).navHostEnterTransition, - exitTransition = Animations(myContext).navHostExitTransition, - popEnterTransition = Animations(myContext).navHostPopEnterTransition, - popExitTransition = Animations(myContext).navHostPopExitTransition - ){ - composable(route = "HomePage", content = { HomePage(navCtrl)}) - composable(route = "SystemManage", content = { SystemManage() }) - composable(route = "ManagedProfile", content = {ManagedProfile()}) - composable(route = "Permissions", content = { DpmPermissions(navCtrl)}) - composable(route = "ApplicationManage", content = { ApplicationManage()}) - composable(route = "UserRestriction", content = { UserRestriction()}) - composable(route = "UserManage", content = { UserManage()}) - composable(route = "Password", content = { Password()}) - composable(route = "AppSetting", content = { AppSetting(navCtrl)}) - composable(route = "Network", content = {Network()}) - composable(route = "ActivateManagedProfile", content = {ActivateManagedProfile(navCtrl)}) - composable(route = "ShizukuActivate", content = {ShizukuActivate()}) + val profileNotActivated = !profileInited&&isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent))) + if(profileNotActivated){ + myDpm.setProfileEnabled(myComponent) + sharedPref.edit().putBoolean("ManagedProfileActivated",true).apply() + Toast.makeText(myContext, myContext.getString(R.string.work_profile_activated), Toast.LENGTH_SHORT).show() } - if(!inited&&jumpToActivateProfile){navCtrl.navigate("ActivateManagedProfile");inited=true} } } @Composable -fun HomePage(navCtrl:NavHostController){ +private fun HomePage(navCtrl:NavHostController){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val activateType = if(isDeviceOwner(myDpm)){"Device Owner"} - else if(isProfileOwner(myDpm)){if(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)){stringResource(R.string.work_profile)}else{"Profile Owner"}} - else if(myDpm.isAdminActive(myComponent)){"Device Admin"} else{""} - caCert = byteArrayOf() - Column(modifier = Modifier.verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally) { + else if(isProfileOwner(myDpm)){ + stringResource(if(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)){R.string.work_profile_owner}else{R.string.profile_owner}) + } + else if(myDpm.isAdminActive(myComponent)){"Device Admin"}else{""} + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + Spacer(Modifier.padding(vertical = 18.dp)) + Text(text = stringResource(R.string.app_name), style = typography.headlineLarge, modifier = Modifier.padding(start = 10.dp)) + Spacer(Modifier.padding(vertical = 8.dp)) Row( modifier = Modifier .fillMaxWidth() @@ -204,11 +152,20 @@ fun HomePage(navCtrl:NavHostController){ if(activateType!=""){ Text(text = activateType, color = colorScheme.onPrimaryContainer, modifier = Modifier.padding(start = 2.dp)) } } } - HomePageItem(R.string.device_ctrl, R.drawable.mobile_phone_fill0, "DeviceControl", navCtrl) - if(VERSION.SDK_INT>=24){HomePageItem(R.string.network, R.drawable.wifi_fill0, "Network",navCtrl)} - HomePageItem(R.string.work_profile, R.drawable.work_fill0, "ManagedProfile",navCtrl) + HomePageItem(R.string.device_ctrl, R.drawable.mobile_phone_fill0, "SystemManage", navCtrl) + if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm))||isProfileOwner(myDpm)){ HomePageItem(R.string.network, R.drawable.wifi_fill0, "Network",navCtrl) } + if( + (VERSION.SDK_INT<24&&!isDeviceOwner(myDpm))||( + VERSION.SDK_INT>=24&&(myDpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)|| + (isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent))) + ) + ){ + HomePageItem(R.string.work_profile, R.drawable.work_fill0, "ManagedProfile",navCtrl) + } HomePageItem(R.string.app_manage, R.drawable.apps_fill0, "ApplicationManage", navCtrl) - HomePageItem(R.string.user_restrict, R.drawable.manage_accounts_fill0, "UserRestriction", navCtrl) + if(VERSION.SDK_INT>=24){ + HomePageItem(R.string.user_restrict, R.drawable.manage_accounts_fill0, "UserRestriction", navCtrl) + } HomePageItem(R.string.user_manage,R.drawable.account_circle_fill0,"UserManage",navCtrl) HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0, "Password",navCtrl) HomePageItem(R.string.setting, R.drawable.info_fill0, "AppSetting",navCtrl) @@ -250,22 +207,13 @@ fun HomePageItem(name:Int, imgVector:Int, navTo:String, myNav:NavHostController) @Stable fun sections(bgColor:Color=colorScheme.primaryContainer,onClick:()->Unit={},clickable:Boolean=false):Modifier{ val backgroundColor = if(isSystemInDarkTheme()){bgColor.copy(0.3F)}else{bgColor.copy(0.8F)} - return if(!LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE).getBoolean("isWear",false)){ - Modifier + return Modifier .fillMaxWidth() .padding(horizontal = 8.dp, vertical = 4.dp) .clip(RoundedCornerShape(14.dp)) .clickable(onClick=onClick, enabled = clickable) .background(color = backgroundColor) .padding(vertical = 10.dp, horizontal = 10.dp) - }else{ - Modifier - .fillMaxWidth() - .padding(horizontal = 3.dp, vertical = 3.dp) - .clip(RoundedCornerShape(10.dp)) - .clickable(onClick=onClick, enabled = clickable) - .background(color = backgroundColor) - .padding(vertical = 2.dp, horizontal = 3.dp) - } + } diff --git a/app/src/main/java/com/binbin/androidowner/Setting.kt b/app/src/main/java/com/binbin/androidowner/Setting.kt index 17976d4..0dd583f 100644 --- a/app/src/main/java/com/binbin/androidowner/Setting.kt +++ b/app/src/main/java/com/binbin/androidowner/Setting.kt @@ -4,16 +4,13 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build.VERSION -import androidx.compose.foundation.clickable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon +import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.Switch -import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -21,83 +18,112 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.binbin.androidowner.dpm.* +import com.binbin.androidowner.ui.Animations +import com.binbin.androidowner.ui.NavIcon +import com.binbin.androidowner.ui.SubPageItem +import com.binbin.androidowner.ui.SwitchItem +@OptIn(ExperimentalMaterial3Api::class) @Composable fun AppSetting(navCtrl:NavHostController){ - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - val myContext = LocalContext.current - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val bodyTextStyle = if(isWear){typography.bodyMedium}else{typography.bodyLarge} - val titleColor = colorScheme.onPrimaryContainer - val pkgInfo = myContext.packageManager.getPackageInfo(myContext.packageName,0) - val verCode = pkgInfo.versionCode - val verName = pkgInfo.versionName - Column(modifier = sections()) { - Row(modifier = Modifier.fillMaxSize().padding(horizontal = 3.dp),horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { - Text(text = "Wear", style = typography.titleLarge, color = titleColor) - Switch( - checked = isWear, - onCheckedChange = { - sharedPref.edit().putBoolean("isWear",!isWear).apply() - navCtrl.navigateUp() - } - ) - } - if(VERSION.SDK_INT>=32){ - Row(modifier = Modifier.fillMaxWidth().padding(horizontal = 3.dp),horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { - Text(text = stringResource(R.string.dynamic_color), style = typography.titleLarge, color = titleColor) - Switch( - checked = sharedPref.getBoolean("dynamicColor",false), - onCheckedChange = { - sharedPref.edit().putBoolean("dynamicColor",!sharedPref.getBoolean("dynamicColor",false)).apply() - navCtrl.navigateUp() - } - ) - } - Text(text = stringResource(R.string.dynamic_color_desc), style = if(isWear){typography.bodyMedium}else{typography.bodyLarge}) - } + val localNavCtrl = rememberNavController() + val backStackEntry by localNavCtrl.currentBackStackEntryAsState() + val titleMap = mapOf( + "About" to R.string.about + ) + Scaffold( + topBar = { + TopAppBar( + title = {Text(text = stringResource(titleMap[backStackEntry?.destination?.route]?:R.string.setting))}, + navigationIcon = {NavIcon{if(backStackEntry?.destination?.route=="Home"){navCtrl.navigateUp()}else{localNavCtrl.navigateUp()}}}, + colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surfaceVariant) + ) } - Column(modifier = sections()) { - Column( - modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 12.dp) - ) { - Text(text = stringResource(R.string.about), style = typography.headlineSmall, color = titleColor) - Text(text = stringResource(R.string.app_name)+" v$verName ($verCode)", style = bodyTextStyle) - Text(text = stringResource(R.string.about_desc), style = bodyTextStyle) - } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .clickable { shareLink(myContext, "https://github.com/BinTianqi/AndroidOwner/blob/master/Guide.md") } - .padding(start = 8.dp, bottom = 4.dp) - ){ - Icon( - painter = painterResource(id = R.drawable.open_in_new), - contentDescription = null, - modifier = Modifier.padding(start = 6.dp, end = 10.dp), - tint = colorScheme.primary - ) - Text(text = stringResource(R.string.user_guide), style = typography.titleLarge, color = colorScheme.onPrimaryContainer, modifier = Modifier.padding(bottom = 2.dp)) - } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .clickable { shareLink(myContext, "https://github.com/BinTianqi/AndroidOwner") } - .padding(start = 8.dp, bottom = 4.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.open_in_new), - contentDescription = null, - modifier = Modifier.padding(start = 6.dp, end = 10.dp), - tint = colorScheme.primary - ) - Text(text = stringResource(R.string.source_code), style = typography.titleLarge, color = colorScheme.onPrimaryContainer, modifier = Modifier.padding(bottom = 2.dp)) - } + ){ + NavHost( + navController = localNavCtrl, startDestination = "Home", + enterTransition = Animations().navHostEnterTransition, + exitTransition = Animations().navHostExitTransition, + popEnterTransition = Animations().navHostPopEnterTransition, + popExitTransition = Animations().navHostPopExitTransition, + modifier = Modifier + .background(color = if(isSystemInDarkTheme()) { colorScheme.background }else{ colorScheme.primary.copy(alpha = 0.05F) }) + .padding(top = it.calculateTopPadding()) + ){ + composable(route = "Home"){Home(localNavCtrl)} + composable(route = "Settings"){Settings()} + composable(route = "About"){About()} + } + } +} + +@Composable +private fun Home(navCtrl: NavHostController){ + Column(modifier = Modifier.fillMaxSize()){ + SubPageItem(R.string.setting,""){navCtrl.navigate("Settings")} + SubPageItem(R.string.about,""){navCtrl.navigate("About")} + } +} + +@Composable +private fun Settings(){ + val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + SwitchItem( + R.string.dynamic_color, stringResource(R.string.dynamic_color_desc),null, + {sharedPref.getBoolean("dynamicColor",false)},{sharedPref.edit().putBoolean("dynamicColor",it).apply()} + ) + } +} + +@Composable +private fun About(){ + val myContext = LocalContext.current + val pkgInfo = myContext.packageManager.getPackageInfo(myContext.packageName,0) + val verCode = pkgInfo.versionCode + val verName = pkgInfo.versionName + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.about), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text = stringResource(R.string.app_name)+" v$verName ($verCode)") + Text(text = stringResource(R.string.about_desc)) + Spacer(Modifier.padding(vertical = 5.dp)) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { shareLink(myContext, "https://github.com/BinTianqi/AndroidOwner/blob/master/Guide.md") } + .padding(start = 8.dp, bottom = 4.dp) + ){ + Icon( + painter = painterResource(id = R.drawable.open_in_new), + contentDescription = null, + modifier = Modifier.padding(start = 6.dp, end = 10.dp), + tint = colorScheme.primary + ) + Text(text = stringResource(R.string.user_guide), style = typography.titleLarge, color = colorScheme.onPrimaryContainer, modifier = Modifier.padding(bottom = 2.dp)) + } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { shareLink(myContext, "https://github.com/BinTianqi/AndroidOwner") } + .padding(start = 8.dp, bottom = 4.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.open_in_new), + contentDescription = null, + modifier = Modifier.padding(start = 6.dp, end = 10.dp), + tint = colorScheme.primary + ) + Text(text = stringResource(R.string.source_code), style = typography.titleLarge, color = colorScheme.onPrimaryContainer, modifier = Modifier.padding(bottom = 2.dp)) } - Spacer(Modifier.padding(vertical = 30.dp)) } } diff --git a/app/src/main/java/com/binbin/androidowner/Utils.kt b/app/src/main/java/com/binbin/androidowner/Utils.kt index 16f8975..9f16913 100644 --- a/app/src/main/java/com/binbin/androidowner/Utils.kt +++ b/app/src/main/java/com/binbin/androidowner/Utils.kt @@ -24,3 +24,23 @@ fun uriToStream( catch(e: IOException){ Toast.makeText(context, "IO异常", Toast.LENGTH_SHORT).show() } }else{ Toast.makeText(context, "空URI", Toast.LENGTH_SHORT).show() } } + +fun List.toText():String{ + var output = "" + var isFirst = true + for(each in listIterator()){ + if(isFirst){isFirst=false}else{output+="\n"} + output+=each + } + return output +} + +fun Set.toText():String{ + var output = "" + var isFirst = true + for(each in iterator()){ + if(isFirst){isFirst=false}else{output+="\n"} + output+=each + } + return output +} diff --git a/app/src/main/java/com/binbin/androidowner/dpm/ApplicationManage.kt b/app/src/main/java/com/binbin/androidowner/dpm/ApplicationManage.kt index 14ffd3e..a08699b 100644 --- a/app/src/main/java/com/binbin/androidowner/dpm/ApplicationManage.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/ApplicationManage.kt @@ -23,20 +23,16 @@ import android.widget.Toast import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.focusable -import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState 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.* import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager @@ -46,8 +42,14 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController import com.binbin.androidowner.R -import com.binbin.androidowner.ui.RadioButtonItem +import com.binbin.androidowner.toText +import com.binbin.androidowner.ui.* import com.binbin.androidowner.uriToStream import kotlinx.coroutines.delay import java.io.IOException @@ -59,225 +61,186 @@ private var crossProfilePkg = mutableSetOf() private var keepUninstallPkg = mutableListOf() private var permittedIme = mutableListOf() private var permittedAccessibility = mutableListOf() +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun ApplicationManage(){ - val myContext = LocalContext.current +fun ApplicationManage(navCtrl:NavHostController){ val focusMgr = LocalFocusManager.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) var pkgName by rememberSaveable{ mutableStateOf("") } - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val bodyTextStyle = if(isWear){ typography.bodyMedium }else{ typography.bodyLarge } - val titleColor = colorScheme.onPrimaryContainer - Column{ - TextField( - value = pkgName, - onValueChange = { pkgName = it }, - label = { Text(stringResource(R.string.package_name)) }, - modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) - ) - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { - if(VERSION.SDK_INT>=24&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ - Text(text = stringResource(R.string.scope_is_work_profile), style = bodyTextStyle, textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp)) + val localNavCtrl = rememberNavController() + val backStackEntry by localNavCtrl.currentBackStackEntryAsState() + val titleMap = mapOf( + "BlockUninstall" to R.string.block_uninstall, + "UserControlDisabled" to R.string.ucd, + "PermissionManage" to R.string.permission_manage, + "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, + "IME" to R.string.permitted_ime, + "KeepUninstalled" to R.string.keep_uninstalled_pkgs, + "InstallApp" to R.string.install_app, + "UninstallApp" to R.string.uninstall_app, + "ClearAppData" to R.string.clear_app_data, + "DefaultDialer" to R.string.set_default_dialer, + ) + Scaffold( + topBar = { + TopAppBar( + title = {Text(text = stringResource(titleMap[backStackEntry?.destination?.route]?:R.string.app_manage))}, + navigationIcon = {NavIcon{if(backStackEntry?.destination?.route=="Home"){navCtrl.navigateUp()}else{localNavCtrl.navigateUp()}}}, + colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surfaceVariant) + ) } - - Button( - onClick = { - val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - intent.setData(Uri.parse("package:$pkgName")) - startActivity(myContext,intent,null) - }, - modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp) - ){ - Text(stringResource(R.string.app_info)) - } - - if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - AppManageItem( - R.string.suspend,R.string.place_holder, - {try{ myDpm.isPackageSuspended(myComponent,pkgName) } - catch(e:NameNotFoundException){ false } - catch(e:IllegalArgumentException){ false }} - ) { b -> myDpm.setPackagesSuspended(myComponent, arrayOf(pkgName), b) } - } - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - AppManageItem(R.string.hide,R.string.isapphidden_desc, {myDpm.isApplicationHidden(myComponent,pkgName)}) {b-> myDpm.setApplicationHidden(myComponent, pkgName, b)} - } - if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - AppManageItem( - R.string.always_on_vpn,R.string.place_holder,{pkgName == myDpm.getAlwaysOnVpnPackage(myComponent)}) {b-> - try { - myDpm.setAlwaysOnVpnPackage(myComponent, pkgName, b) - } catch(e: java.lang.UnsupportedOperationException) { - Toast.makeText(myContext, myContext.getString(R.string.unsupported), Toast.LENGTH_SHORT).show() - } catch(e: NameNotFoundException) { - Toast.makeText(myContext, myContext.getString(R.string.not_installed), Toast.LENGTH_SHORT).show() - } + ){ paddingValues-> + Column(modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding())){ + if(backStackEntry?.destination?.route!="InstallApp"){ + TextField( + value = pkgName, + onValueChange = { pkgName = it }, + label = { Text(stringResource(R.string.package_name)) }, + modifier = Modifier.fillMaxWidth(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) + ) + } + NavHost( + navController = localNavCtrl, startDestination = "Home", + enterTransition = Animations().navHostEnterTransition, + exitTransition = Animations().navHostExitTransition, + popEnterTransition = Animations().navHostPopEnterTransition, + popExitTransition = Animations().navHostPopExitTransition, + modifier = Modifier.background(color = if(isSystemInDarkTheme()) { colorScheme.background }else{ colorScheme.primary.copy(alpha = 0.05F) }) + ){ + composable(route = "Home"){Home(localNavCtrl,pkgName)} + composable(route = "BlockUninstall"){BlockUninstall(pkgName)} + composable(route = "UserControlDisabled"){UserCtrlDisabledPkg(pkgName)} + composable(route = "PermissionManage"){PermissionManage(pkgName)} + composable(route = "CrossProfilePackage"){CrossProfilePkg(pkgName)} + composable(route = "CrossProfileWidget"){CrossProfileWidget(pkgName)} + composable(route = "CredentialManagePolicy"){CredentialManagePolicy(pkgName)} + composable(route = "Accessibility"){PermittedAccessibility(pkgName)} + composable(route = "IME"){PermittedIME(pkgName)} + composable(route = "KeepUninstalled"){KeepUninstalledApp(pkgName)} + composable(route = "InstallApp"){InstallApp()} + composable(route = "UninstallApp"){UninstallApp(pkgName)} + composable(route = "ClearAppData"){ClearAppData(pkgName)} + composable(route = "DefaultDialer"){DefaultDialerApp(pkgName)} } } - - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - Column{ - var state by remember{mutableStateOf(myDpm.isUninstallBlocked(myComponent,pkgName))} - Text(text = stringResource(R.string.block_uninstall), style = typography.titleLarge, color = titleColor) - Text(stringResource(R.string.current_state, stringResource(if(state){R.string.enabled}else{R.string.disabled}))) - Text(text = stringResource(R.string.sometimes_get_wrong_block_uninstall_state), style = bodyTextStyle) - Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { - Button( - onClick = { - focusMgr.clearFocus() - myDpm.setUninstallBlocked(myComponent,pkgName,true) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - state = myDpm.isUninstallBlocked(myComponent,pkgName) - }, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.enable)) - } - Button( - onClick = { - focusMgr.clearFocus() - myDpm.setUninstallBlocked(myComponent,pkgName,false) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - state = myDpm.isUninstallBlocked(myComponent,pkgName) - }, - modifier = Modifier.fillMaxWidth(0.96F) - ){ - Text(stringResource(R.string.disable)) - } - } - } - } - - if(VERSION.SDK_INT>=30&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - UserCtrlDisabledPkg(pkgName) - } - - if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - PermissionManage(pkgName) - } - - if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ - CrossProfilePkg(pkgName) - } - - if(isProfileOwner(myDpm)){ - CrossProfileWidget(pkgName) - } - - if(VERSION.SDK_INT>=34&&isDeviceOwner(myDpm)){ - CredentialManagePolicy(pkgName) - } - - if(isProfileOwner(myDpm)||isDeviceOwner(myDpm)){ - PermittedAccessibility(pkgName) - } - - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - PermittedIME(pkgName) - } - - if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ - KeepUninstalledApp(pkgName) - } - - if(VERSION.SDK_INT>=28){ - Button( - onClick = { - val executor = Executors.newCachedThreadPool() - val onClear = DevicePolicyManager.OnClearApplicationUserDataListener { pkg: String, succeed: Boolean -> - Looper.prepare() - focusMgr.clearFocus() - val toastText = if(pkg!=""){"$pkg\n"}else{""} + myContext.getString(R.string.clear_data) + myContext.getString(if(succeed){R.string.success}else{R.string.fail}) - Toast.makeText(myContext, toastText, Toast.LENGTH_SHORT).show() - Looper.loop() - } - myDpm.clearApplicationUserData(myComponent,pkgName,executor,onClear) - }, - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), - modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp) - ) { - Text(stringResource(R.string.clear_app_data)) - } - } - - if(VERSION.SDK_INT>=34){ - Button( - onClick = { - try{ - myDpm.setDefaultDialerApplication(pkgName) - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }catch(e:IllegalArgumentException){ - Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() - } - }, - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), - modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp) - ) { - Text(stringResource(R.string.set_default_dialer)) - } - } - - UninstallApp(pkgName) - - InstallApp() - - Spacer(Modifier.padding(30.dp)) - } } } @Composable -private fun AppManageItem( - itemName:Int, - itemDesc:Int, - getMethod:()->Boolean, - setMethod:(b:Boolean)->Unit -){ - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - var enabled by remember{mutableStateOf(getMethod())} - enabled = getMethod() - Column(modifier = if(sharedPref.getBoolean("isWear",false)){Modifier.fillMaxWidth(0.65F)}else{Modifier}){ - Text(text = stringResource(itemName), style = typography.titleLarge, color = colorScheme.onPrimaryContainer) - if(itemDesc!=R.string.place_holder){ Text(stringResource(itemDesc)) } +private fun Home(navCtrl:NavHostController, pkgName: String){ + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Spacer(Modifier.padding(vertical = 5.dp)) + if(VERSION.SDK_INT>=24&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ + Text(text = stringResource(R.string.scope_is_work_profile), textAlign = TextAlign.Center,modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp)) + Spacer(Modifier.padding(vertical = 5.dp)) } - Switch( - checked = enabled, - onCheckedChange = { setMethod(!enabled); enabled=getMethod() } - ) + SubPageItem(R.string.app_info,""){ + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + intent.setData(Uri.parse("package:$pkgName")) + startActivity(myContext,intent,null) + } + if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + SwitchItem( + R.string.suspend,"",null, + { + try{ myDpm.isPackageSuspended(myComponent,pkgName) } + catch(e:NameNotFoundException){ false } + catch(e:IllegalArgumentException){ false } + }, + {myDpm.setPackagesSuspended(myComponent, arrayOf(pkgName), it)} + ) + } + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + SwitchItem( + R.string.hide, stringResource(R.string.isapphidden_desc),null, + {myDpm.isApplicationHidden(myComponent,pkgName)},{myDpm.setApplicationHidden(myComponent, pkgName, it)} + ) + } + if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + SwitchItem( + R.string.always_on_vpn,"",null,{pkgName == myDpm.getAlwaysOnVpnPackage(myComponent)}, + { + try { + myDpm.setAlwaysOnVpnPackage(myComponent, pkgName, it) + } catch(e: java.lang.UnsupportedOperationException) { + Toast.makeText(myContext, myContext.getString(R.string.unsupported), Toast.LENGTH_SHORT).show() + } catch(e: NameNotFoundException) { + Toast.makeText(myContext, myContext.getString(R.string.not_installed), Toast.LENGTH_SHORT).show() + } + } + ) + } + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + SubPageItem(R.string.block_uninstall,""){navCtrl.navigate("BlockUninstall")} + } + if(VERSION.SDK_INT>=30&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + SubPageItem(R.string.ucd,""){navCtrl.navigate("UserControlDisabled")} + } + if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + SubPageItem(R.string.permission_manage,""){navCtrl.navigate("PermissionManage")} + } + if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ + SubPageItem(R.string.cross_profile_package,""){navCtrl.navigate("CrossProfilePackage")} + } + if(isProfileOwner(myDpm)){ + SubPageItem(R.string.cross_profile_widget,""){navCtrl.navigate("CrossProfileWidget")} + } + if(VERSION.SDK_INT>=34&&isDeviceOwner(myDpm)){ + SubPageItem(R.string.credential_manage_policy,""){navCtrl.navigate("CredentialManagePolicy")} + } + if(isProfileOwner(myDpm)||isDeviceOwner(myDpm)){ + SubPageItem(R.string.permitted_accessibility_app,""){navCtrl.navigate("Accessibility")} + } + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + SubPageItem(R.string.permitted_ime,""){navCtrl.navigate("IME")} + } + if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ + SubPageItem(R.string.keep_uninstalled_pkgs,""){navCtrl.navigate("KeepUninstalled")} + } + if(VERSION.SDK_INT>=28){ + SubPageItem(R.string.clear_app_data,""){navCtrl.navigate("ClearAppData")} + } + SubPageItem(R.string.install_app,""){navCtrl.navigate("InstallApp")} + SubPageItem(R.string.uninstall_app,""){navCtrl.navigate("UninstallApp")} + if(VERSION.SDK_INT>=34){ + SubPageItem(R.string.set_default_dialer,""){navCtrl.navigate("DefaultDialer")} + } + Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun UserCtrlDisabledPkg(pkgName:String){ +private fun UserCtrlDisabledPkg(pkgName:String){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ var pkgList = myDpm.getUserControlDisabledPackages(myComponent) var listText by remember{mutableStateOf("")} val refresh = { pkgList = myDpm.getUserControlDisabledPackages(myComponent) - listText = "" - var count = pkgList.size - for(pkg in pkgList){ count-=1; listText+=pkg; if(count>0){listText+="\n"} } + listText = pkgList.toText() } var inited by remember{mutableStateOf(false)} if(!inited){refresh();inited=true} - Text(text = stringResource(R.string.ucd), style = typography.titleLarge) + 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(scrollAnim())){ + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(Animations().animateListSize)){ 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 = { @@ -314,12 +277,56 @@ fun UserCtrlDisabledPkg(pkgName:String){ ){ Text(stringResource(R.string.clear_list)) } + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +private fun BlockUninstall(pkgName: String){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + var state by remember{mutableStateOf(myDpm.isUninstallBlocked(myComponent,pkgName))} + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.block_uninstall), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Text(stringResource(R.string.current_state, stringResource(if(state){R.string.enabled}else{R.string.disabled}))) + Spacer(Modifier.padding(vertical = 3.dp)) + Text(text = stringResource(R.string.sometimes_get_wrong_block_uninstall_state)) + Spacer(Modifier.padding(vertical = 5.dp)) + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setUninstallBlocked(myComponent,pkgName,true) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + state = myDpm.isUninstallBlocked(myComponent,pkgName) + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text(stringResource(R.string.enable)) + } + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setUninstallBlocked(myComponent,pkgName,false) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + state = myDpm.isUninstallBlocked(myComponent,pkgName) + }, + modifier = Modifier.fillMaxWidth(0.96F) + ){ + Text(stringResource(R.string.disable)) + } + } + Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun PermissionManage(pkgName: String){ +private fun PermissionManage(pkgName: String){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) @@ -329,19 +336,23 @@ fun PermissionManage(pkgName: String){ PERMISSION_GRANT_STATE_GRANTED to stringResource(R.string.granted), PERMISSION_GRANT_STATE_DENIED to stringResource(R.string.denied) ) - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ var inputPermission by remember{mutableStateOf("android.permission.")} var currentState by remember{mutableStateOf(grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)])} - Text(text = stringResource(R.string.permission_manage), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.permission_manage), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) OutlinedTextField( value = inputPermission, label = { Text(stringResource(R.string.permission))}, onValueChange = {inputPermission = it}, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + modifier = Modifier.focusable().fillMaxWidth() ) + Spacer(Modifier.padding(vertical = 5.dp)) Text(stringResource(R.string.current_state, currentState?:stringResource(R.string.unknown))) + Spacer(Modifier.padding(vertical = 5.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { @@ -371,27 +382,26 @@ fun PermissionManage(pkgName: String){ ) { Text(stringResource(R.string.decide_by_user)) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun CrossProfilePkg(pkgName: String){ +private fun CrossProfilePkg(pkgName: String){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column{ - Text(text = stringResource(R.string.cross_profile_package), style = typography.titleLarge) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Text(text = stringResource(R.string.cross_profile_package), style = typography.headlineLarge) var list by remember{mutableStateOf("")} val refresh = { crossProfilePkg = myDpm.getCrossProfilePackages(myComponent) - list = "" - var count = crossProfilePkg.size - for(each in crossProfilePkg){ count-=1; list+=each; if(count>0){list+="\n"} } + list = crossProfilePkg.toText() } - var inited by remember{mutableStateOf(false)} - if(!inited){refresh();inited=true} - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ + LaunchedEffect(Unit){refresh()} + Text(text = stringResource(R.string.app_list_is)) + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(Animations().animateListSize)){ Text(text = if(list==""){stringResource(R.string.none)}else{list}) } Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ @@ -416,30 +426,33 @@ fun CrossProfilePkg(pkgName: String){ Text(stringResource(R.string.remove)) } } + Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -fun CrossProfileWidget(pkgName: String){ +private fun CrossProfileWidget(pkgName: String){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ var pkgList: MutableList var list by remember{mutableStateOf("")} val refresh = { pkgList = myDpm.getCrossProfileWidgetProviders(myComponent) - list = "" - var count = pkgList.size - for(each in pkgList){ count-=1; list+=each; if(count>0){list+="\n"}} + list = pkgList.toText() } - var inited by remember{mutableStateOf(false)} - if(!inited){refresh();inited=true} - Text(text = stringResource(R.string.cross_profile_widget), style = typography.titleLarge) + LaunchedEffect(Unit){refresh()} + 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.cross_profile_widget_desc)) - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text = stringResource(R.string.app_list_is)) + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(Animations().animateListSize)){ Text(text = if(list==""){stringResource(R.string.none)}else{list}) } + Spacer(Modifier.padding(vertical = 5.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { @@ -460,12 +473,13 @@ fun CrossProfileWidget(pkgName: String){ Text(stringResource(R.string.remove)) } } + Spacer(Modifier.padding(vertical = 10.dp)) } } @SuppressLint("NewApi") @Composable -fun CredentialManagePolicy(pkgName: String){ +private fun CredentialManagePolicy(pkgName: String){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val focusMgr = LocalFocusManager.current @@ -478,30 +492,28 @@ fun CredentialManagePolicy(pkgName: String){ credentialList = policy?.packageNames ?: mutableSetOf() credentialList = credentialList.toMutableSet() } - val refreshText = { - credentialListText = "" - var count = credentialList.size - for(item in credentialList){ count-=1; credentialListText+=item; if(count>0){credentialListText+="\n"} } - } - var inited by remember{mutableStateOf(false)} - if(!inited){refreshPolicy(); refreshText(); inited = true} - Column{ - Text(text = stringResource(R.string.credential_manage_policy), style = typography.titleLarge) + LaunchedEffect(Unit){refreshPolicy(); credentialListText = credentialList.toText()} + 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) + Spacer(Modifier.padding(vertical = 5.dp)) RadioButtonItem(stringResource(R.string.none),{policyType==-1},{policyType=-1}) RadioButtonItem(stringResource(R.string.blacklist),{policyType==PACKAGE_POLICY_BLOCKLIST},{policyType=PACKAGE_POLICY_BLOCKLIST}) RadioButtonItem(stringResource(R.string.whitelist),{policyType==PACKAGE_POLICY_ALLOWLIST},{policyType=PACKAGE_POLICY_ALLOWLIST}) RadioButtonItem(stringResource(R.string.whitelist_and_system_app),{policyType==PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM},{policyType=PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM}) + Spacer(Modifier.padding(vertical = 5.dp)) AnimatedVisibility(policyType!=-1) { Column { - Text("应用列表") + Text(stringResource(R.string.app_list_is)) SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ Text(text = if(credentialListText!=""){ credentialListText }else{ stringResource(R.string.none) }) } + Spacer(Modifier.padding(vertical = 10.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { if(pkgName!=""){credentialList.add(pkgName)} - refreshText() + credentialListText = credentialList.toText() }, modifier = Modifier.fillMaxWidth(0.49F) ) { @@ -510,7 +522,7 @@ fun CredentialManagePolicy(pkgName: String){ Button( onClick = { if(pkgName!=""){credentialList.remove(pkgName)} - refreshText() + credentialListText = credentialList.toText() }, modifier = Modifier.fillMaxWidth(0.96F) ) { @@ -533,48 +545,47 @@ fun CredentialManagePolicy(pkgName: String){ Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() }finally { refreshPolicy() - refreshText() + credentialListText = credentialList.toText() } }, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.apply)) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -fun PermittedAccessibility(pkgName: String){ +private fun PermittedAccessibility(pkgName: String){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column { - Text(text = stringResource(R.string.permitted_accessibility_app), style = typography.titleLarge) + 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("") } - val refreshList = { - listText = "" - var count = permittedAccessibility.size - for(eachAccessibility in permittedAccessibility){ count-=1; listText+=eachAccessibility; if(count>0){listText+="\n"} } - } - var inited by remember{mutableStateOf(false)} - if(!inited){ + LaunchedEffect(Unit){ val getList = myDpm.getPermittedAccessibilityServices(myComponent) if(getList!=null){ permittedAccessibility = getList } - refreshList(); inited=true + listText = permittedAccessibility.toText() } - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){ + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text = stringResource(R.string.app_list_is)) + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(Animations().animateListSize)){ 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); refreshList()}, + onClick = { permittedAccessibility.add(pkgName); listText = permittedAccessibility.toText()}, modifier = Modifier.fillMaxWidth(0.49F) ) { Text(stringResource(R.string.add)) } Button( - onClick = { permittedAccessibility.remove(pkgName); refreshList() }, + onClick = { permittedAccessibility.remove(pkgName); listText = permittedAccessibility.toText() }, modifier = Modifier.fillMaxWidth(0.96F) ) { Text(stringResource(R.string.remove)) @@ -586,46 +597,46 @@ fun PermittedAccessibility(pkgName: String){ Toast.makeText(myContext, if(myDpm.setPermittedAccessibilityServices(myComponent, permittedAccessibility)){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() val getList = myDpm.getPermittedAccessibilityServices(myComponent) if(getList!=null){ permittedAccessibility = getList } - refreshList() + listText = permittedAccessibility.toText() }, modifier = Modifier.fillMaxWidth() ) { Text(text = stringResource(R.string.apply)) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -fun PermittedIME(pkgName: String){ +private fun PermittedIME(pkgName: String){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column { - Text(text = stringResource(R.string.permitted_ime), style = typography.titleLarge) + 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("") } - val refreshList = { - imeListText = "" - for(eachIme in permittedIme){ imeListText += "$eachIme \n" } - } - var inited by remember{mutableStateOf(false)} - if(!inited){ + LaunchedEffect(Unit){ val getList = myDpm.getPermittedInputMethods(myComponent) if(getList!=null){ permittedIme = getList } - refreshList();inited=true + 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); refreshList() }, + onClick = { permittedIme.add(pkgName); imeListText = permittedIme.toText() }, modifier = Modifier.fillMaxWidth(0.49F) ) { Text(stringResource(R.string.add)) } Button( - onClick = { permittedIme.remove(pkgName); refreshList()}, + onClick = { permittedIme.remove(pkgName); imeListText = permittedIme.toText() }, modifier = Modifier.fillMaxWidth(0.96F) ) { Text(stringResource(R.string.remove)) @@ -637,44 +648,43 @@ fun PermittedIME(pkgName: String){ Toast.makeText(myContext, if(myDpm.setPermittedInputMethods(myComponent, permittedIme)){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() val getList = myDpm.getPermittedInputMethods(myComponent) if(getList!=null){ permittedIme = getList } - refreshList() + imeListText = permittedIme.toText() }, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.apply)) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun KeepUninstalledApp(pkgName: String){ +private fun KeepUninstalledApp(pkgName: String){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ - Text(text = stringResource(R.string.keep_uninstalled_pkgs), style = typography.titleLarge) + 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("")} - val refresh = { - listText = "" - var count = keepUninstallPkg.size - for(each in keepUninstallPkg){ count-=1; listText+=each; if(count>0){listText+="\n"} } - } - var inited by remember{mutableStateOf(false)} - if(!inited){ + LaunchedEffect(Unit){ val getList = myDpm.getKeepUninstalledPackages(myComponent) if(getList!=null){ keepUninstallPkg = getList } - refresh(); inited=true + listText = keepUninstallPkg.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(listText==""){stringResource(R.string.none)}else{listText}) } + Spacer(Modifier.padding(vertical = 5.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { keepUninstallPkg.add(pkgName) - refresh() + listText = keepUninstallPkg.toText() }, modifier = Modifier.fillMaxWidth(0.49F) ){ @@ -683,7 +693,7 @@ fun KeepUninstalledApp(pkgName: String){ Button( onClick = { keepUninstallPkg.remove(pkgName) - refresh() + listText = keepUninstallPkg.toText() }, modifier = Modifier.fillMaxWidth(0.96F) ){ @@ -696,21 +706,25 @@ fun KeepUninstalledApp(pkgName: String){ myDpm.setKeepUninstalledPackages(myComponent, keepUninstallPkg) val getList = myDpm.getKeepUninstalledPackages(myComponent) if(getList!=null){ keepUninstallPkg = getList } + listText = keepUninstallPkg.toText() Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() }, modifier = Modifier.fillMaxWidth() ){ Text(stringResource(R.string.apply)) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -fun UninstallApp(pkgName: String){ +private fun UninstallApp(pkgName: String){ val myContext = LocalContext.current - Column{ - Text(text = stringResource(R.string.uninstall_app), style = typography.titleLarge) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.uninstall_app), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Column(modifier = Modifier.fillMaxWidth()){ Button( onClick = { val intent = Intent(myContext,PackageInstallerReceiver::class.java) @@ -718,7 +732,7 @@ fun UninstallApp(pkgName: String){ val pkgInstaller = myContext.packageManager.packageInstaller pkgInstaller.uninstall(pkgName, intentSender) }, - modifier = Modifier.fillMaxWidth(0.49F) + modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.silent_uninstall)) } @@ -728,7 +742,7 @@ fun UninstallApp(pkgName: String){ intent.setData(Uri.parse("package:$pkgName")) myContext.startActivity(intent) }, - modifier = Modifier.fillMaxWidth(0.96F) + modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.request_uninstall)) } @@ -737,11 +751,13 @@ fun UninstallApp(pkgName: String){ } @Composable -fun InstallApp(){ +private fun InstallApp(){ val myContext = LocalContext.current val focusMgr = LocalFocusManager.current - Column{ - Text(text = stringResource(R.string.install_app), style = typography.titleLarge) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.install_app), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { focusMgr.clearFocus() @@ -755,12 +771,13 @@ fun InstallApp(){ Text(stringResource(R.string.select_apk)) } var selected by remember{mutableStateOf(false)} - LaunchedEffect(selected){while(true){ delay(500); selected = apkUri!=null}} + LaunchedEffect(selected){while(true){ delay(800); selected = apkUri!=null}} AnimatedVisibility(selected) { - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Spacer(Modifier.padding(vertical = 3.dp)) + Column(modifier = Modifier.fillMaxWidth()){ Button( onClick = { uriToStream(myContext, apkUri){stream -> installPackage(myContext,stream)} }, - modifier = Modifier.fillMaxWidth(0.49F) + modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.silent_install)) } @@ -771,7 +788,7 @@ fun InstallApp(){ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) myContext.startActivity(intent) }, - modifier = Modifier.fillMaxWidth(0.96F) + modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.request_install)) } @@ -780,6 +797,59 @@ fun InstallApp(){ } } +@SuppressLint("NewApi") +@Composable +private fun ClearAppData(pkgName: String){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Button( + onClick = { + val executor = Executors.newCachedThreadPool() + val onClear = DevicePolicyManager.OnClearApplicationUserDataListener { pkg: String, succeed: Boolean -> + Looper.prepare() + focusMgr.clearFocus() + val toastText = if(pkg!=""){"$pkg\n"}else{""} + myContext.getString(R.string.clear_data) + myContext.getString(if(succeed){R.string.success}else{R.string.fail}) + Toast.makeText(myContext, toastText, Toast.LENGTH_SHORT).show() + Looper.loop() + } + myDpm.clearApplicationUserData(myComponent,pkgName,executor,onClear) + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp) + ) { + Text(stringResource(R.string.clear_app_data)) + } + } +} + +@SuppressLint("NewApi") +@Composable +private fun DefaultDialerApp(pkgName: String){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Button( + onClick = { + try{ + myDpm.setDefaultDialerApplication(pkgName) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }catch(e:IllegalArgumentException){ + Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() + } + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp) + ) { + Text(stringResource(R.string.set_default_dialer)) + } + } +} + @Throws(IOException::class) private fun installPackage(context: Context, inputStream: InputStream){ val packageInstaller = context.packageManager.packageInstaller diff --git a/app/src/main/java/com/binbin/androidowner/dpm/DPM.kt b/app/src/main/java/com/binbin/androidowner/dpm/DPM.kt index 8ce03d7..a789480 100644 --- a/app/src/main/java/com/binbin/androidowner/dpm/DPM.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/DPM.kt @@ -7,7 +7,6 @@ import androidx.activity.result.ActivityResultLauncher lateinit var getCaCert: ActivityResultLauncher -lateinit var createUser: ActivityResultLauncher lateinit var createManagedProfile: ActivityResultLauncher lateinit var getApk: ActivityResultLauncher lateinit var getUserIcon: ActivityResultLauncher diff --git a/app/src/main/java/com/binbin/androidowner/dpm/ManagedProfile.kt b/app/src/main/java/com/binbin/androidowner/dpm/ManagedProfile.kt index 2fd2b33..762ca8e 100644 --- a/app/src/main/java/com/binbin/androidowner/dpm/ManagedProfile.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/ManagedProfile.kt @@ -9,19 +9,14 @@ import android.os.Build.VERSION import android.widget.Toast import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.focusable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState 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.Button +import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Switch -import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -32,68 +27,91 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController import com.binbin.androidowner.R -import com.binbin.androidowner.ui.CheckBoxItem +import com.binbin.androidowner.ui.* + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ManagedProfile(navCtrl: NavHostController) { + val localNavCtrl = rememberNavController() + val backStackEntry by localNavCtrl.currentBackStackEntryAsState() + val titleMap = mapOf( + "OrgOwnedWorkProfile" to R.string.org_owned_work_profile, + "CreateWorkProfile" to R.string.create_work_profile, + "SuspendPersonalApp" to R.string.suspend_personal_app, + "IntentFilter" to R.string.intent_filter, + "OrgID" to R.string.org_id + ) + Scaffold( + topBar = { + TopAppBar( + title = {Text(text = stringResource(titleMap[backStackEntry?.destination?.route]?:R.string.work_profile))}, + navigationIcon = {NavIcon{if(backStackEntry?.destination?.route=="Home"){navCtrl.navigateUp()}else{localNavCtrl.navigateUp()}}}, + colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surfaceVariant) + ) + } + ){ + NavHost( + navController = localNavCtrl, startDestination = "Home", + enterTransition = Animations().navHostEnterTransition, + exitTransition = Animations().navHostExitTransition, + popEnterTransition = Animations().navHostPopEnterTransition, + popExitTransition = Animations().navHostPopExitTransition, + modifier = Modifier + .background(color = if(isSystemInDarkTheme()) { colorScheme.background }else{ colorScheme.primary.copy(alpha = 0.05F) }) + .padding(top = it.calculateTopPadding()) + ){ + composable(route = "Home"){Home(localNavCtrl)} + composable(route = "OrgOwnedWorkProfile"){OrgOwnedProfile()} + composable(route = "CreateWorkProfile"){CreateWorkProfile()} + composable(route = "SuspendPersonalApp"){SuspendPersonalApp()} + composable(route = "IntentFilter"){IntentFilter()} + composable(route = "OrgID"){OrgID()} + } + } +} @Composable -fun ManagedProfile() { +private fun Home(navCtrl: NavHostController){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val bodyTextStyle = if(isWear){ typography.bodyMedium}else{ typography.bodyLarge} - val titleColor = colorScheme.onPrimaryContainer Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ - Column{ - Text(text = stringResource(R.string.info), style = typography.titleLarge, color = titleColor) - if(VERSION.SDK_INT>=24){ - if(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ - Text(text = stringResource(R.string.is_already_work_profile)) - }else{ - Text(text = stringResource(R.string.able_to_create_work_profile, myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)), style = bodyTextStyle) - if(isDeviceOwner(myDpm)){ - Text(text = stringResource(R.string.device_owner_cannot_create_work_profile), style = bodyTextStyle) - } - } - } - if(VERSION.SDK_INT>=30){ - Text(text = stringResource(R.string.is_org_owned_profile, myDpm.isOrganizationOwnedDeviceWithManagedProfile), style = bodyTextStyle) - } - } - - if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)&&!myDpm.isOrganizationOwnedDeviceWithManagedProfile){ - OrgOwnedProfile() - + Spacer(Modifier.padding(vertical = 10.dp)) + if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ + SubPageItem(R.string.org_owned_work_profile,""){navCtrl.navigate("OrgOwnedWorkProfile")} } if(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE))){ - CreateWorkProfile() + SubPageItem(R.string.create_work_profile,""){navCtrl.navigate("CreateWorkProfile")} } - if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile){ - SuspendPersonalApp() + SubPageItem(R.string.suspend_personal_app,""){navCtrl.navigate("SuspendPersonalApp")} } - if(isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)))){ - IntentFilter() + SubPageItem(R.string.intent_filter,""){navCtrl.navigate("IntentFilter")} } - if(VERSION.SDK_INT>=31&&(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent))){ - OrgID() + SubPageItem(R.string.org_id,""){navCtrl.navigate("OrgID")} } - Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -fun CreateWorkProfile(){ +private fun CreateWorkProfile(){ val myContext = LocalContext.current val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column{ - Text(text = stringResource(R.string.work_profile), style = typography.titleLarge) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.create_work_profile), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) var skipEncrypt by remember{mutableStateOf(false)} if(VERSION.SDK_INT>=24){CheckBoxItem(stringResource(R.string.skip_encryption),{skipEncrypt},{skipEncrypt=!skipEncrypt})} + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { try { @@ -101,7 +119,7 @@ fun CreateWorkProfile(){ if(VERSION.SDK_INT>=23){ intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,myComponent) }else{ - intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,"com.binbin.androidowner") + intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, myContext.packageName) } if(VERSION.SDK_INT>=24){intent.putExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION,skipEncrypt)} if(VERSION.SDK_INT>=33){intent.putExtra(EXTRA_PROVISIONING_ALLOW_OFFLINE,true)} @@ -117,39 +135,51 @@ fun CreateWorkProfile(){ } } +@SuppressLint("NewApi") @Composable -fun OrgOwnedProfile(){ - Column{ - Text(text = stringResource(R.string.org_owned_work_profile), color = colorScheme.onTertiaryContainer, style = typography.titleLarge) - SelectionContainer { - Text(text = "使用ADB执行以下命令,或者使用Shizuku") - Text( - text = stringResource(R.string.activate_org_profile_command, Binder.getCallingUid()/100000), - color = colorScheme.onTertiaryContainer - ) +private fun OrgOwnedProfile(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.org_owned_work_profile), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text = stringResource(R.string.is_org_owned_profile,myDpm.isOrganizationOwnedDeviceWithManagedProfile)) + Spacer(Modifier.padding(vertical = 5.dp)) + if(!myDpm.isOrganizationOwnedDeviceWithManagedProfile){ + SelectionContainer { + Text( + text = stringResource(R.string.activate_org_profile_command, Binder.getCallingUid()/100000), + color = colorScheme.onTertiaryContainer + ) + } } } } @SuppressLint("NewApi") @Composable -fun OrgID(){ +private fun OrgID(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val focusMgr = LocalFocusManager.current - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ var orgId by remember{mutableStateOf("")} - Text(text = stringResource(R.string.org_id), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.org_id), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) OutlinedTextField( value = orgId, onValueChange = {orgId=it}, label = {Text(stringResource(R.string.org_id))}, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + modifier = Modifier.focusable().fillMaxWidth() ) + Spacer(Modifier.padding(vertical = 2.dp)) AnimatedVisibility(orgId.length !in 6..64) { Text(text = stringResource(R.string.length_6_to_64)) } + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { myDpm.setOrganizationId(orgId) @@ -160,32 +190,27 @@ fun OrgID(){ ){ Text(stringResource(R.string.apply)) } - Text(text = stringResource(R.string.get_specific_id_after_set_org_id)) + Spacer(Modifier.padding(vertical = 5.dp)) + Information{Text(text = stringResource(R.string.get_specific_id_after_set_org_id))} } } @SuppressLint("NewApi") @Composable -fun SuspendPersonalApp(){ +private fun SuspendPersonalApp(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ - Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){ - var suspended by remember{mutableStateOf(false)} - suspended = myDpm.getPersonalAppsSuspendedReasons(myComponent)!=PERSONAL_APPS_NOT_SUSPENDED - Text(text = stringResource(R.string.suspend_personal_app), style = typography.titleLarge) - Switch( - checked = suspended, - onCheckedChange ={ - myDpm.setPersonalAppsSuspended(myComponent,!suspended) - suspended = myDpm.getPersonalAppsSuspendedReasons(myComponent)!=PERSONAL_APPS_NOT_SUSPENDED - } - ) - } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + SwitchItem( + R.string.suspend_personal_app,"",null,{myDpm.getPersonalAppsSuspendedReasons(myComponent)!=PERSONAL_APPS_NOT_SUSPENDED}, + {myDpm.setPersonalAppsSuspended(myComponent,it)} + ) var time by remember{mutableStateOf("")} time = myDpm.getManagedProfileMaximumTimeOff(myComponent).toString() + Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.profile_max_time_off), style = typography.titleLarge) Text(text = stringResource(R.string.profile_max_time_out_desc)) Text(text = stringResource(R.string.personal_app_suspended_because_timeout, myDpm.getPersonalAppsSuspendedReasons(myComponent)==PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT)) @@ -209,21 +234,24 @@ fun SuspendPersonalApp(){ } @Composable -fun IntentFilter(){ +private fun IntentFilter(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ var action by remember{mutableStateOf("")} - Text(text = stringResource(R.string.intent_filter), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.intent_filter), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) OutlinedTextField( value = action, onValueChange = {action = it}, label = {Text("Action")}, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + modifier = Modifier.focusable().fillMaxWidth() ) + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter(action), FLAG_PARENT_CAN_ACCESS_MANAGED) @@ -242,11 +270,10 @@ fun IntentFilter(){ ) { Text(stringResource(R.string.add_intent_filter_personal_to_work)) } + Spacer(Modifier.padding(vertical = 2.dp)) Button( onClick = { myDpm.clearCrossProfileIntentFilters(myComponent) - myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_MANAGED_CAN_ACCESS_PARENT) - myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_PARENT_CAN_ACCESS_MANAGED) Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() }, modifier = Modifier.fillMaxWidth() @@ -255,28 +282,3 @@ fun IntentFilter(){ } } } - -@Composable -fun ActivateManagedProfile(navCtrl: NavHostController){ - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val sharedPref = myContext.getSharedPreferences("data", Context.MODE_PRIVATE) - myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_MANAGED_CAN_ACCESS_PARENT) - myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter("com.binbin.androidowner.MAIN_ACTION"), FLAG_PARENT_CAN_ACCESS_MANAGED) - Column(modifier = Modifier.verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally){ - Text(text = stringResource(R.string.activate_managed_profile), style = typography.titleLarge) - Text(text = stringResource(R.string.activate_managed_profile_desc)) - Button( - onClick = { - myDpm.setProfileEnabled(myComponent) - navCtrl.popBackStack("HomePage",false) - sharedPref.edit().putBoolean("ManagedProfileActivated",true).apply() - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth().padding(8.dp) - ) { - Text(stringResource(R.string.activate)) - } - } -} diff --git a/app/src/main/java/com/binbin/androidowner/dpm/Network.kt b/app/src/main/java/com/binbin/androidowner/dpm/Network.kt index badce23..8b97e1f 100644 --- a/app/src/main/java/com/binbin/androidowner/dpm/Network.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/Network.kt @@ -2,11 +2,11 @@ package com.binbin.androidowner.dpm import android.annotation.SuppressLint import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyManager.* import android.app.admin.WifiSsidPolicy import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST import android.content.ComponentName -import android.content.Context import android.net.wifi.WifiSsid import android.os.Build.VERSION import android.telephony.TelephonyManager @@ -17,21 +17,14 @@ import android.widget.Toast import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.focusable -import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState 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.Button +import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -40,79 +33,136 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.net.toUri +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController import com.binbin.androidowner.R -import com.binbin.androidowner.ui.RadioButtonItem +import com.binbin.androidowner.toText +import com.binbin.androidowner.ui.* var ssidSet = mutableSetOf() +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun Network(){ +fun Network(navCtrl: NavHostController){ + val localNavCtrl = rememberNavController() + val backStackEntry by localNavCtrl.currentBackStackEntryAsState() + val titleMap = mapOf( + "Home" to R.string.network, + "MinWifiSecurityLevel" to R.string.min_wifi_security_level, + "WifiSsidPolicy" to R.string.wifi_ssid_policy, + "PrivateDNS" to R.string.private_dns, + "NetLog" to R.string.retrieve_net_logs, + "WifiKeypair" to R.string.wifi_keypair, + "APN" to R.string.apn_settings + ) + Scaffold( + topBar = { + TopAppBar( + title = {Text(text = stringResource(titleMap[backStackEntry?.destination?.route]?:R.string.network))}, + navigationIcon = {NavIcon{if(backStackEntry?.destination?.route=="Home"){navCtrl.navigateUp()}else{localNavCtrl.navigateUp()}}}, + colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surfaceVariant) + ) + } + ){ + NavHost( + navController = localNavCtrl, startDestination = "Home", + enterTransition = Animations().navHostEnterTransition, + exitTransition = Animations().navHostExitTransition, + popEnterTransition = Animations().navHostPopEnterTransition, + popExitTransition = Animations().navHostPopExitTransition, + modifier = Modifier + .background(color = if(isSystemInDarkTheme()) { colorScheme.background }else{ colorScheme.primary.copy(alpha = 0.05F) }) + .padding(top = it.calculateTopPadding()) + ){ + composable(route = "Home"){Home(localNavCtrl)} + composable(route = "Switches"){Switches()} + composable(route = "MinWifiSecurityLevel"){WifiSecLevel()} + composable(route = "WifiSsidPolicy"){WifiSsidPolicy()} + composable(route = "PrivateDNS"){PrivateDNS()} + composable(route = "NetLog"){NetLog()} + composable(route = "WifiKeypair"){WifiKeypair()} + composable(route = "APN"){APN()} + } + } +} + +@Composable +private fun Home(navCtrl:NavHostController){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val bodyTextStyle = if(isWear){ typography.bodyMedium }else{ typography.bodyLarge } Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ - + Spacer(Modifier.padding(vertical = 5.dp)) if(VERSION.SDK_INT>=24){ val wifimac = try { myDpm.getWifiMacAddress(myComponent).toString() }catch(e:SecurityException){ "没有权限" } - Text(text = "WiFi MAC: $wifimac",modifier=Modifier.fillMaxWidth(), textAlign = TextAlign.Center,style=bodyTextStyle) + Text(text = "WiFi MAC: $wifimac", modifier = Modifier.padding(start = 15.dp)) } - + Spacer(Modifier.padding(vertical = 3.dp)) + if(VERSION.SDK_INT>=30){ + SubPageItem(R.string.options,""){navCtrl.navigate("Switches")} + } + if(VERSION.SDK_INT>=33){ + SubPageItem(R.string.min_wifi_security_level,""){navCtrl.navigate("MinWifiSecurityLevel")} + } + if(VERSION.SDK_INT>=33&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ + SubPageItem(R.string.wifi_ssid_policy,""){navCtrl.navigate("WifiSsidPolicy")} + } + if(VERSION.SDK_INT>=29&&isDeviceOwner(myDpm)){ + SubPageItem(R.string.private_dns,""){navCtrl.navigate("PrivateDNS")} + } + if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)))){ + SubPageItem(R.string.retrieve_net_logs,""){navCtrl.navigate("NetLog")} + } + if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + SubPageItem(R.string.wifi_keypair,""){navCtrl.navigate("WifiKeypair")} + } + if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ + SubPageItem(R.string.apn_settings,""){navCtrl.navigate("APN")} + } + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +private fun Switches(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column(modifier = Modifier.fillMaxSize()){ + Spacer(Modifier.padding(vertical = 5.dp)) if(VERSION.SDK_INT>=33&&isDeviceOwner(myDpm)){ - DeviceCtrlItem( - R.string.preferential_network_service,R.string.developing,R.drawable.globe_fill0, - {myDpm.isPreferentialNetworkServiceEnabled},{b -> myDpm.isPreferentialNetworkServiceEnabled = b} + SwitchItem( + R.string.preferential_network_service, stringResource(R.string.developing),R.drawable.globe_fill0, + {myDpm.isPreferentialNetworkServiceEnabled},{myDpm.isPreferentialNetworkServiceEnabled = it} ) } if(VERSION.SDK_INT>=30&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ - DeviceCtrlItem(R.string.wifi_lockdown,R.string.place_holder,R.drawable.wifi_password_fill0, - {myDpm.hasLockdownAdminConfiguredNetworks(myComponent)},{b -> myDpm.setConfiguredNetworksLockdownState(myComponent,b)} + SwitchItem(R.string.wifi_lockdown,"",R.drawable.wifi_password_fill0, + {myDpm.hasLockdownAdminConfiguredNetworks(myComponent)},{myDpm.setConfiguredNetworksLockdownState(myComponent,it)} ) } - if(VERSION.SDK_INT>=33){ - WifiSecLevel() - } - - if(VERSION.SDK_INT>=33&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ - WifiSsidPolicy() - } - - if(VERSION.SDK_INT>=29&&isDeviceOwner(myDpm)){ - PrivateDNS() - } - - if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)))){ - NetLog() - } - - if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - WifiKeypair() - } - - if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ - APN() - } - - Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun WifiSecLevel(){ +private fun WifiSecLevel(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ var selectedWifiSecLevel by remember{mutableIntStateOf(myDpm.minimumRequiredWifiSecurityLevel)} - Text(text = stringResource(R.string.min_wifi_security_level), style = typography.titleLarge) - RadioButtonItem(stringResource(R.string.wifi_security_level_open), {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_OPEN}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_OPEN}) - RadioButtonItem("WEP, WPA(2)-PSK", {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_PERSONAL}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_PERSONAL}) - RadioButtonItem("WPA-EAP", {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP}) - RadioButtonItem("WPA3-192bit", {selectedWifiSecLevel==DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192}, {selectedWifiSecLevel= DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192}) + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.min_wifi_security_level), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + RadioButtonItem(stringResource(R.string.wifi_security_level_open), {selectedWifiSecLevel==WIFI_SECURITY_OPEN}, {selectedWifiSecLevel= WIFI_SECURITY_OPEN}) + RadioButtonItem("WEP, WPA(2)-PSK", {selectedWifiSecLevel==WIFI_SECURITY_PERSONAL}, {selectedWifiSecLevel= WIFI_SECURITY_PERSONAL}) + RadioButtonItem("WPA-EAP", {selectedWifiSecLevel==WIFI_SECURITY_ENTERPRISE_EAP}, {selectedWifiSecLevel= WIFI_SECURITY_ENTERPRISE_EAP}) + RadioButtonItem("WPA3-192bit", {selectedWifiSecLevel==WIFI_SECURITY_ENTERPRISE_192}, {selectedWifiSecLevel= WIFI_SECURITY_ENTERPRISE_192}) + Spacer(Modifier.padding(vertical = 5.dp)) Button( enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile), onClick = { @@ -128,11 +178,11 @@ fun WifiSecLevel(){ @SuppressLint("NewApi") @Composable -fun WifiSsidPolicy(){ +private fun WifiSsidPolicy(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val focusMgr = LocalFocusManager.current - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ var policy = myDpm.wifiSsidPolicy var selectedPolicyType by remember{mutableIntStateOf(policy?.policyType ?: -1)} var inputSsid by remember{mutableStateOf("")} @@ -142,33 +192,32 @@ fun WifiSsidPolicy(){ selectedPolicyType = policy?.policyType ?: -1 ssidSet = policy?.ssids ?: mutableSetOf() } - val refreshList = { - ssidList = "" - var count = ssidSet.size - for(ssid in ssidSet){ count-=1; ssidList+=ssid; if(count>0){ssidList+="\n"} } - } - var inited by remember{mutableStateOf(false)} - if(!inited){ refreshPolicy(); refreshList(); inited=true } - Text(text = stringResource(R.string.wifi_ssid_policy), style = typography.titleLarge) + LaunchedEffect(Unit){refreshPolicy(); ssidList=ssidSet.toText()} + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.wifi_ssid_policy), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) RadioButtonItem(stringResource(R.string.none),{selectedPolicyType==-1},{selectedPolicyType=-1}) RadioButtonItem(stringResource(R.string.whitelist),{selectedPolicyType==WIFI_SSID_POLICY_TYPE_ALLOWLIST},{selectedPolicyType=WIFI_SSID_POLICY_TYPE_ALLOWLIST}) RadioButtonItem(stringResource(R.string.blacklist),{selectedPolicyType==WIFI_SSID_POLICY_TYPE_DENYLIST},{selectedPolicyType=WIFI_SSID_POLICY_TYPE_DENYLIST}) Column(modifier = Modifier.animateContentSize(scrollAnim()).horizontalScroll(rememberScrollState())){ if(ssidList!=""){ + Spacer(Modifier.padding(vertical = 5.dp)) Text(stringResource(R.string.ssid_list_is)) SelectionContainer{ Text(text = ssidList, color = colorScheme.onPrimaryContainer) } } } + Spacer(Modifier.padding(vertical = 5.dp)) OutlinedTextField( value = inputSsid, label = { Text("SSID")}, onValueChange = {inputSsid = it}, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + modifier = Modifier.focusable().fillMaxWidth() ) + Spacer(Modifier.padding(vertical = 5.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { @@ -178,7 +227,7 @@ fun WifiSsidPolicy(){ Toast.makeText(myContext, myContext.getString(R.string.already_exist), Toast.LENGTH_SHORT).show() }else{ ssidSet.add(WifiSsid.fromBytes(inputSsid.toByteArray())) - refreshList() + ssidList = ssidSet.toText() } inputSsid = "" }, @@ -193,7 +242,7 @@ fun WifiSsidPolicy(){ }else if(WifiSsid.fromBytes(inputSsid.toByteArray()) in ssidSet){ ssidSet.remove(WifiSsid.fromBytes(inputSsid.toByteArray())) inputSsid = "" - refreshList() + ssidList = ssidSet.toText() }else{ Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show() } @@ -224,42 +273,48 @@ fun WifiSsidPolicy(){ ) { Text(stringResource(R.string.apply)) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun PrivateDNS(){ +private fun PrivateDNS(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ - Text(text = stringResource(R.string.private_dns), style = typography.titleLarge) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.private_dns), style = typography.headlineLarge) val dnsStatus = mapOf( - DevicePolicyManager.PRIVATE_DNS_MODE_UNKNOWN to stringResource(R.string.unknown), - DevicePolicyManager.PRIVATE_DNS_MODE_OFF to stringResource(R.string.disabled), - DevicePolicyManager.PRIVATE_DNS_MODE_OPPORTUNISTIC to stringResource(R.string.auto), - DevicePolicyManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME to stringResource(R.string.dns_provide_hostname) + PRIVATE_DNS_MODE_UNKNOWN to stringResource(R.string.unknown), + PRIVATE_DNS_MODE_OFF to stringResource(R.string.disabled), + PRIVATE_DNS_MODE_OPPORTUNISTIC to stringResource(R.string.auto), + PRIVATE_DNS_MODE_PROVIDER_HOSTNAME to stringResource(R.string.dns_provide_hostname) ) val operationResult = mapOf( - DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR to stringResource(R.string.success), - DevicePolicyManager.PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING to stringResource(R.string.host_not_serving_dns_tls), - DevicePolicyManager.PRIVATE_DNS_SET_ERROR_FAILURE_SETTING to stringResource(R.string.fail) + PRIVATE_DNS_SET_NO_ERROR to stringResource(R.string.success), + PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING to stringResource(R.string.host_not_serving_dns_tls), + PRIVATE_DNS_SET_ERROR_FAILURE_SETTING to stringResource(R.string.fail) ) var status by remember{mutableStateOf(dnsStatus[myDpm.getGlobalPrivateDnsMode(myComponent)])} + Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.current_state, status?:stringResource(R.string.unknown))) - Button( - onClick = { - val result = myDpm.setGlobalPrivateDnsModeOpportunistic(myComponent) - Toast.makeText(myContext, operationResult[result], Toast.LENGTH_SHORT).show() - status = dnsStatus[myDpm.getGlobalPrivateDnsMode(myComponent)] - }, - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.set_to_auto)) + AnimatedVisibility(visible = myDpm.getGlobalPrivateDnsMode(myComponent)!=PRIVATE_DNS_MODE_OPPORTUNISTIC) { + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = { + val result = myDpm.setGlobalPrivateDnsModeOpportunistic(myComponent) + Toast.makeText(myContext, operationResult[result], Toast.LENGTH_SHORT).show() + status = dnsStatus[myDpm.getGlobalPrivateDnsMode(myComponent)] + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.set_to_auto)) + } } - Spacer(Modifier.padding(vertical = 3.dp)) + Spacer(Modifier.padding(vertical = 10.dp)) var inputHost by remember{mutableStateOf(myDpm.getGlobalPrivateDnsHost(myComponent) ?: "")} OutlinedTextField( value = inputHost, @@ -267,8 +322,9 @@ fun PrivateDNS(){ label = {Text(stringResource(R.string.dns_hostname))}, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + modifier = Modifier.focusable().fillMaxWidth() ) + Spacer(Modifier.padding(vertical = 3.dp)) Button( onClick = { focusMgr.clearFocus() @@ -293,21 +349,18 @@ fun PrivateDNS(){ @SuppressLint("NewApi") @Composable -fun NetLog(){ +private fun NetLog(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column{ - Text(text = stringResource(R.string.retrieve_net_logs), style = typography.titleLarge) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.retrieve_net_logs), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.developing)) - Row(modifier=Modifier.fillMaxWidth().padding(horizontal=8.dp),horizontalArrangement=Arrangement.SpaceBetween,verticalAlignment=Alignment.CenterVertically){ - var checked by remember{mutableStateOf(myDpm.isNetworkLoggingEnabled(myComponent))} - Text(text = stringResource(R.string.enabled), style = typography.titleLarge) - Switch( - checked = checked, - onCheckedChange = {myDpm.setNetworkLoggingEnabled(myComponent,!checked);checked = myDpm.isNetworkLoggingEnabled(myComponent)} - ) - } + Spacer(Modifier.padding(vertical = 5.dp)) + SwitchItem(R.string.enable,"",null,{myDpm.isNetworkLoggingEnabled(myComponent)},{myDpm.setNetworkLoggingEnabled(myComponent,it)}) + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { val log = myDpm.retrieveNetworkLogs(myComponent,1234567890) @@ -328,23 +381,27 @@ fun NetLog(){ @SuppressLint("NewApi") @Composable -fun WifiKeypair(){ +private fun WifiKeypair(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val focusMgr = LocalFocusManager.current - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ var keyPair by remember{mutableStateOf("")} - Text(text = stringResource(R.string.wifi_keypair), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.wifi_keypair), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) OutlinedTextField( value = keyPair, label = { Text(stringResource(R.string.keypair))}, onValueChange = {keyPair = it}, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + modifier = Modifier.focusable().fillMaxWidth() ) + Spacer(Modifier.padding(vertical = 5.dp)) val isExist = try{myDpm.isKeyPairGrantedToWifiAuth(keyPair)}catch(e:java.lang.IllegalArgumentException){false} Text(stringResource(R.string.already_exist)+":$isExist") + Spacer(Modifier.padding(vertical = 5.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( onClick = { @@ -370,21 +427,22 @@ fun WifiKeypair(){ @SuppressLint("NewApi") @Composable -fun APN(){ +private fun APN(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ val setting = myDpm.getOverrideApns(myComponent) var inputNum by remember{mutableStateOf("0")} var nextStep by remember{mutableStateOf(false)} val builder = Builder() - Text(text = stringResource(R.string.apn_settings), style = typography.titleLarge) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){ - Text(text = stringResource(R.string.enable), style = typography.titleLarge) - Switch(checked = myDpm.isOverrideApnEnabled(myComponent), onCheckedChange = {myDpm.setOverrideApnsEnabled(myComponent,it)}) - } + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.apn_settings), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text = stringResource(id = R.string.developing)) + Spacer(Modifier.padding(vertical = 5.dp)) + SwitchItem(R.string.enable,"",null,{myDpm.isOverrideApnEnabled(myComponent)},{myDpm.setOverrideApnsEnabled(myComponent,it)}) Text(text = stringResource(R.string.total_apn_amount, setting.size)) if(setting.size>0){ Text(text = stringResource(R.string.select_a_apn_or_create, setting.size)) @@ -736,6 +794,6 @@ fun APN(){ } } } - Text(text = stringResource(id = R.string.developing)) + Spacer(Modifier.padding(vertical = 30.dp)) } } diff --git a/app/src/main/java/com/binbin/androidowner/dpm/Password.kt b/app/src/main/java/com/binbin/androidowner/dpm/Password.kt index b10e684..e0be52a 100644 --- a/app/src/main/java/com/binbin/androidowner/dpm/Password.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/Password.kt @@ -11,23 +11,16 @@ import android.os.Build.VERSION import android.widget.Toast import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -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.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Check 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 import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource @@ -35,196 +28,186 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController import com.binbin.androidowner.R -import com.binbin.androidowner.ui.CheckBoxItem -import com.binbin.androidowner.ui.RadioButtonItem +import com.binbin.androidowner.ui.* + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Password(navCtrl: NavHostController){ + val localNavCtrl = rememberNavController() + val backStackEntry by localNavCtrl.currentBackStackEntryAsState() + val titleMap = mapOf( + "ResetPasswordToken" to R.string.reset_password_token, + "PasswordInfo" to R.string.password_info, + "ResetPassword" to R.string.reset_password, + "RequirePasswordComplexity" to R.string.required_password_complexity, + "KeyguardDisabledFeatures" to R.string.keyguard_disabled_features, + "MaxTimeToLock" to R.string.max_time_to_lock, + "PasswordTimeout" to R.string.pwd_timeout, + "MaxPasswordFail" to R.string.max_pwd_fail, + "PasswordHistoryLength" to R.string.pwd_history, + "RequirePasswordQuality" to R.string.required_password_quality, + ) + Scaffold( + topBar = { + TopAppBar( + title = {Text(text = stringResource(titleMap[backStackEntry?.destination?.route]?:R.string.password_and_keyguard))}, + navigationIcon = {NavIcon{if(backStackEntry?.destination?.route=="Home"){navCtrl.navigateUp()}else{localNavCtrl.navigateUp()}}}, + colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surfaceVariant) + ) + } + ){ + NavHost( + navController = localNavCtrl, startDestination = "Home", + enterTransition = Animations().navHostEnterTransition, + exitTransition = Animations().navHostExitTransition, + popEnterTransition = Animations().navHostPopEnterTransition, + popExitTransition = Animations().navHostPopExitTransition, + modifier = Modifier + .background(color = if(isSystemInDarkTheme()) { colorScheme.background }else{ colorScheme.primary.copy(alpha = 0.05F) }) + .padding(top = it.calculateTopPadding()) + ){ + composable(route = "Home"){Home(localNavCtrl)} + composable(route = "PasswordInfo"){PasswordInfo()} + composable(route = "ResetPasswordToken"){ResetPasswordToken()} + composable(route = "ResetPassword"){ResetPassword()} + composable(route = "RequirePasswordComplexity"){PasswordComplexity()} + composable(route = "KeyguardDisabledFeatures"){KeyguardDisabledFeatures()} + composable(route = "MaxTimeToLock"){ScreenTimeout()} + composable(route = "PasswordTimeout"){PasswordExpiration()} + composable(route = "MaxPasswordFail"){MaxFailedPasswordForWipe()} + composable(route = "PasswordHistoryLength"){PasswordHistoryLength()} + composable(route = "RequirePasswordQuality"){PasswordQuality()} + } + } +} @Composable -fun Password(){ - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val bodyTextStyle = if(isWear){typography.bodyMedium}else{typography.bodyLarge} - val scrollState = rememberScrollState() - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxSize().verticalScroll(scrollState) - ) { - Text( - text = stringResource(R.string.password_warning), - color = colorScheme.onErrorContainer, - style=bodyTextStyle - ) - if(myDpm.isDeviceOwnerApp("com.binbin.androidowner")){ - Column{ - if(VERSION.SDK_INT>=29){ - val passwordComplexity = mapOf( - PASSWORD_COMPLEXITY_NONE to stringResource(R.string.password_complexity_none), - PASSWORD_COMPLEXITY_LOW to stringResource(R.string.password_complexity_low), - PASSWORD_COMPLEXITY_MEDIUM to stringResource(R.string.password_complexity_medium), - PASSWORD_COMPLEXITY_HIGH to stringResource(R.string.password_complexity_high) - ) - val pwdComplex = passwordComplexity[myDpm.passwordComplexity] - Text(text = stringResource(R.string.current_password_complexity_is, pwdComplex?:stringResource(R.string.unknown)),style=bodyTextStyle) - } - if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ - Text(stringResource(R.string.is_password_sufficient, myDpm.isActivePasswordSufficient),style=bodyTextStyle) - } - val pwdFailedAttempts = myDpm.currentFailedPasswordAttempts - Text(text = stringResource(R.string.password_failed_attempts_is, pwdFailedAttempts),style=bodyTextStyle) - if(VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ - val unifiedPwd = myDpm.isUsingUnifiedPassword(myComponent) - Text(stringResource(R.string.is_using_unified_password, unifiedPwd),style=bodyTextStyle) - } - } - } +private fun Home(navCtrl:NavHostController){ + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + SubPageItem(R.string.password_info,""){navCtrl.navigate("PasswordInfo")} if(VERSION.SDK_INT>=26){ - ResetPasswordToken() + SubPageItem(R.string.reset_password_token,""){navCtrl.navigate("ResetPasswordToken")} } - - ResetPassword() - - PasswordItem(R.string.max_pwd_fail,R.string.max_pwd_fail_desc,R.string.max_pwd_fail_textfield, false, - {myDpm.getMaximumFailedPasswordsForWipe(null).toString()},{ic -> myDpm.setMaximumFailedPasswordsForWipe(myComponent, ic.toInt()) }) - PasswordItem(R.string.pwd_timeout,R.string.pwd_timeout_desc,R.string.pwd_timeout_textfield,true, - {myDpm.getPasswordExpiration(null).toString()},{ic -> myDpm.setPasswordExpirationTimeout(myComponent, ic.toLong()) }) - PasswordItem(R.string.pwd_history,R.string.pwd_history_desc,R.string.pwd_history_textfield,true, - {myDpm.getPasswordHistoryLength(null).toString()},{ic -> myDpm.setPasswordHistoryLength(myComponent, ic.toInt()) }) - PasswordItem(R.string.max_time_to_lock,R.string.max_time_to_lock_desc,R.string.time_unit_ms,true, - {myDpm.getMaximumTimeToLock(myComponent).toString()},{ic -> myDpm.setMaximumTimeToLock(myComponent,ic.toLong())}) - + SubPageItem(R.string.reset_password,""){navCtrl.navigate("ResetPassword")} if(VERSION.SDK_INT>=31){ - PasswordComplexity() + SubPageItem(R.string.required_password_complexity,""){navCtrl.navigate("RequirePasswordComplexity")} } - - KeyguardDisabledFeatures() - - PasswordQuality() - + SubPageItem(R.string.keyguard_disabled_features,""){navCtrl.navigate("KeyguardDisabledFeatures")} + SubPageItem(R.string.max_time_to_lock,""){navCtrl.navigate("MaxTimeToLock")} + SubPageItem(R.string.pwd_timeout,""){navCtrl.navigate("PasswordTimeout")} + SubPageItem(R.string.max_pwd_fail,""){navCtrl.navigate("MaxPasswordFail")} + SubPageItem(R.string.pwd_history,""){navCtrl.navigate("PasswordHistoryLength")} + SubPageItem(R.string.required_password_quality,""){navCtrl.navigate("RequirePasswordQuality")} Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -private fun PasswordItem( - itemName:Int, - itemDesc:Int, - textFieldLabel:Int, - allowZero:Boolean, - getMethod:()->String, - setMethod:(ic:String)->Unit -){ +private fun PasswordInfo(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val focusMgr = LocalFocusManager.current - Column{ - var inputContent by remember{ mutableStateOf(if(isDeviceOwner(myDpm)){getMethod()}else{""}) } - var ableToApply by remember{ mutableStateOf(inputContent!=""&&((inputContent=="0"&&allowZero)||inputContent!="0")) } - Text(text = stringResource(itemName), style = typography.titleLarge,color = colorScheme.onPrimaryContainer) - Text(text= stringResource(itemDesc),modifier=Modifier.padding(vertical = 2.dp), style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}) - if(!isWear){Spacer(Modifier.padding(vertical = 2.dp))} - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth() - ){ - OutlinedTextField( - value = inputContent, - label = { Text(stringResource(textFieldLabel))}, - onValueChange = { - inputContent = it - ableToApply = inputContent!=""&&((inputContent=="0"&&allowZero)||inputContent!="0") - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - enabled = isDeviceOwner(myDpm), - modifier = Modifier.focusable().fillMaxWidth(), - trailingIcon = { - Icon( - imageVector = Icons.Outlined.Check, contentDescription = "OK", - modifier = Modifier - .clip(RoundedCornerShape(50)) - .clickable(onClick = {focusMgr.clearFocus() ; setMethod(inputContent)}, enabled = isDeviceOwner(myDpm)&&ableToApply) - .padding(2.dp) - ) - } + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.password_info), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + if(VERSION.SDK_INT>=29){ + val passwordComplexity = mapOf( + PASSWORD_COMPLEXITY_NONE to stringResource(R.string.password_complexity_none), + PASSWORD_COMPLEXITY_LOW to stringResource(R.string.password_complexity_low), + PASSWORD_COMPLEXITY_MEDIUM to stringResource(R.string.password_complexity_medium), + PASSWORD_COMPLEXITY_HIGH to stringResource(R.string.password_complexity_high) ) + val pwdComplex = passwordComplexity[myDpm.passwordComplexity] + Text(text = stringResource(R.string.current_password_complexity_is, pwdComplex?:stringResource(R.string.unknown))) + } + if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ + Text(stringResource(R.string.is_password_sufficient, myDpm.isActivePasswordSufficient)) + } + val pwdFailedAttempts = myDpm.currentFailedPasswordAttempts + Text(text = stringResource(R.string.password_failed_attempts_is, pwdFailedAttempts)) + if(VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ + val unifiedPwd = myDpm.isUsingUnifiedPassword(myComponent) + Text(stringResource(R.string.is_using_unified_password, unifiedPwd)) } } } @SuppressLint("NewApi") @Composable -fun ResetPasswordToken(){ +private fun ResetPasswordToken(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val myByteArray by remember{ mutableStateOf(byteArrayOf(1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0)) } - Column(horizontalAlignment = Alignment.Start) { - Text(text = stringResource(R.string.reset_password_token), style = typography.titleLarge) - Row( + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.reset_password_token), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = { + if(myDpm.clearResetPasswordToken(myComponent)){ Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }else{ Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() } + }, modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ){ - Button( - onClick = { - if(myDpm.clearResetPasswordToken(myComponent)){ Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }else{ Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() } - }, - modifier = Modifier.fillMaxWidth(0.32F), - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) - ) { - Text(stringResource(R.string.clear)) - } - Button( - onClick = { - try { - if(myDpm.setResetPasswordToken(myComponent, myByteArray)){ - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }else{ - Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() - } - }catch(e:SecurityException){ - Toast.makeText(myContext, myContext.getString(R.string.security_exception), Toast.LENGTH_SHORT).show() - } - }, - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), - modifier = Modifier.fillMaxWidth(0.47F) - ) { - Text(stringResource(R.string.set)) - } - Button( - onClick = { - if(!myDpm.isResetPasswordTokenActive(myComponent)){ - try{ activateToken(myContext) } - catch(e:NullPointerException){ Toast.makeText(myContext, myContext.getString(R.string.please_set_a_token), Toast.LENGTH_SHORT).show() } - }else{ Toast.makeText(myContext, "已经激活", Toast.LENGTH_SHORT).show() } - }, - enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), - modifier = Modifier.fillMaxWidth(0.88F) - ) { - Text(stringResource(R.string.activate)) - } + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) + ) { + Text(stringResource(R.string.clear)) } - Text(stringResource(R.string.activate_token_not_required_when_no_password)) + Button( + onClick = { + try { + if(myDpm.setResetPasswordToken(myComponent, myByteArray)){ + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }else{ + Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() + } + }catch(e:SecurityException){ + Toast.makeText(myContext, myContext.getString(R.string.security_exception), Toast.LENGTH_SHORT).show() + } + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.set)) + } + Button( + onClick = { + if(!myDpm.isResetPasswordTokenActive(myComponent)){ + try{ activateToken(myContext) } + catch(e:NullPointerException){ Toast.makeText(myContext, myContext.getString(R.string.please_set_a_token), Toast.LENGTH_SHORT).show() } + }else{ Toast.makeText(myContext, "已经激活", Toast.LENGTH_SHORT).show() } + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.activate)) + } + Spacer(Modifier.padding(vertical = 5.dp)) + Information{Text(stringResource(R.string.activate_token_not_required_when_no_password))} } } @Composable -fun ResetPassword(){ +private fun ResetPassword(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current var newPwd by remember{ mutableStateOf("") } val myByteArray by remember{ mutableStateOf(byteArrayOf(1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0,1,1,4,5,1,4,1,9,1,9,8,1,0)) } - Column{ - var confirmed by remember{ mutableStateOf(false) } - Text(text = stringResource(R.string.reset_password),style = typography.titleLarge) + var confirmed by remember{ mutableStateOf(false) } + var resetPwdFlag by remember{ mutableIntStateOf(0) } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.reset_password),style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) OutlinedTextField( value = newPwd, onValueChange = {newPwd=it}, @@ -232,10 +215,11 @@ fun ResetPassword(){ label = { Text(stringResource(R.string.password))}, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().padding(vertical = 5.dp).fillMaxWidth() + modifier = Modifier.focusable().fillMaxWidth() ) - Text(text = stringResource(R.string.reset_pwd_desc), modifier = Modifier.padding(vertical = 3.dp)) - var resetPwdFlag by remember{ mutableIntStateOf(0) } + Spacer(Modifier.padding(vertical = 3.dp)) + Text(text = stringResource(R.string.reset_pwd_desc)) + Spacer(Modifier.padding(vertical = 5.dp)) if(VERSION.SDK_INT>=23){ RadioButtonItem( stringResource(R.string.do_not_ask_credentials_on_boot), @@ -244,6 +228,7 @@ fun ResetPassword(){ } RadioButtonItem(stringResource(R.string.reset_password_require_entry),{resetPwdFlag==RESET_PASSWORD_REQUIRE_ENTRY}, {resetPwdFlag=RESET_PASSWORD_REQUIRE_ENTRY}) RadioButtonItem(stringResource(R.string.none),{resetPwdFlag==0},{resetPwdFlag=0}) + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { if(newPwd.length>=4||newPwd.isEmpty()){ confirmed=!confirmed @@ -258,6 +243,7 @@ fun ResetPassword(){ ) { Text(text = stringResource(if(confirmed){R.string.cancel}else{R.string.confirm})) } + Spacer(Modifier.padding(vertical = 3.dp)) if(VERSION.SDK_INT>=26){ Button( onClick = { @@ -286,96 +272,248 @@ fun ResetPassword(){ ) { Text(stringResource(R.string.reset_password_deprecated)) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun PasswordComplexity(){ +private fun PasswordComplexity(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - Column{ - val passwordComplexity = mapOf( - PASSWORD_COMPLEXITY_NONE to stringResource(R.string.password_complexity_none), - PASSWORD_COMPLEXITY_LOW to stringResource(R.string.password_complexity_low), - PASSWORD_COMPLEXITY_MEDIUM to stringResource(R.string.password_complexity_medium), - PASSWORD_COMPLEXITY_HIGH to stringResource(R.string.password_complexity_high) - ).toList() - var selectedItem by remember{ mutableIntStateOf(passwordComplexity[0].first) } - if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ - selectedItem=myDpm.requiredPasswordComplexity - } - Text(text = stringResource(R.string.required_password_complexity), style = typography.titleLarge) + val passwordComplexity = mapOf( + PASSWORD_COMPLEXITY_NONE to stringResource(R.string.password_complexity_none), + PASSWORD_COMPLEXITY_LOW to stringResource(R.string.password_complexity_low), + PASSWORD_COMPLEXITY_MEDIUM to stringResource(R.string.password_complexity_medium), + PASSWORD_COMPLEXITY_HIGH to stringResource(R.string.password_complexity_high) + ).toList() + var selectedItem by remember{ mutableIntStateOf(passwordComplexity[0].first) } + if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ + selectedItem=myDpm.requiredPasswordComplexity + } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.required_password_complexity), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) RadioButtonItem(passwordComplexity[0].second,{selectedItem==passwordComplexity[0].first},{selectedItem=passwordComplexity[0].first}) RadioButtonItem(passwordComplexity[1].second,{selectedItem==passwordComplexity[1].first},{selectedItem=passwordComplexity[1].first}) RadioButtonItem(passwordComplexity[2].second,{selectedItem==passwordComplexity[2].first},{selectedItem=passwordComplexity[2].first}) RadioButtonItem(passwordComplexity[3].second,{selectedItem==passwordComplexity[3].first},{selectedItem=passwordComplexity[3].first}) - Text(text = stringResource(R.string.password_ordered_desc), modifier = Modifier.padding(vertical = 3.dp)) - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = { - myDpm.requiredPasswordComplexity = selectedItem - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm)|| isProfileOwner(myDpm), - modifier = Modifier.fillMaxWidth(0.4F) - ) { - Text(text = stringResource(R.string.apply)) - } - Button( - onClick = {myContext.startActivity(Intent(ACTION_SET_NEW_PASSWORD))}, - modifier = Modifier.fillMaxWidth(0.95F) - ){ - Text(stringResource(R.string.require_set_new_password)) - } + Spacer(Modifier.padding(vertical = 5.dp)) + + Button( + onClick = { + myDpm.requiredPasswordComplexity = selectedItem + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)|| isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.apply)) + } + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = {myContext.startActivity(Intent(ACTION_SET_NEW_PASSWORD))}, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.require_set_new_password)) + } + Spacer(Modifier.padding(vertical = 5.dp)) + Information{Text(text = stringResource(R.string.password_ordered_desc))} + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +private fun ScreenTimeout(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + var inputContent by remember{ mutableStateOf(if(isDeviceOwner(myDpm)){myDpm.getMaximumTimeToLock(myComponent).toString()}else{""}) } + var ableToApply by remember{ mutableStateOf(inputContent!="") } + 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 + ableToApply = inputContent!="" + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + enabled = isDeviceOwner(myDpm), + modifier = Modifier.focusable().fillMaxWidth() + ) + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = {focusMgr.clearFocus() ; myDpm.setMaximumTimeToLock(myComponent,inputContent.toLong())}, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.apply)) } } } @Composable -fun KeyguardDisabledFeatures(){ +private fun MaxFailedPasswordForWipe(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column{ - var state by remember{mutableIntStateOf(-1)} - var shortcuts by remember{mutableStateOf(false)} - var biometrics by remember{mutableStateOf(false)} - var iris by remember{mutableStateOf(false)} - var face by remember{mutableStateOf(false)} - var remote by remember{mutableStateOf(false)} - var fingerprint by remember{mutableStateOf(false)} - var agents by remember{mutableStateOf(false)} - var unredacted by remember{mutableStateOf(false)} - var notification by remember{mutableStateOf(false)} - var camera by remember{mutableStateOf(false)} - var widgets by remember{mutableStateOf(false)} - val calculateCustomFeature = { - var calculate = myDpm.getKeyguardDisabledFeatures(myComponent) - if(calculate==0){state=0} - else{ - if(calculate-KEYGUARD_DISABLE_SHORTCUTS_ALL>=0 && VERSION.SDK_INT>=34){shortcuts=true;calculate-= KEYGUARD_DISABLE_SHORTCUTS_ALL } - if(calculate-KEYGUARD_DISABLE_BIOMETRICS>=0&&VERSION.SDK_INT>=28){biometrics=true;calculate -= KEYGUARD_DISABLE_BIOMETRICS } - if(calculate-KEYGUARD_DISABLE_IRIS>=0&&VERSION.SDK_INT>=28){iris=true;calculate -= KEYGUARD_DISABLE_IRIS } - if(calculate-KEYGUARD_DISABLE_FACE>=0&&VERSION.SDK_INT>=28){face=true;calculate -= KEYGUARD_DISABLE_FACE } - if(calculate-KEYGUARD_DISABLE_REMOTE_INPUT>=0&&VERSION.SDK_INT>=24){remote=true;calculate -= KEYGUARD_DISABLE_REMOTE_INPUT } - if(calculate-KEYGUARD_DISABLE_FINGERPRINT>=0){fingerprint=true;calculate -= KEYGUARD_DISABLE_FINGERPRINT } - if(calculate-KEYGUARD_DISABLE_TRUST_AGENTS>=0){agents=true;calculate -= KEYGUARD_DISABLE_TRUST_AGENTS } - if(calculate-KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS>=0){unredacted=true;calculate -= KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS } - if(calculate-KEYGUARD_DISABLE_SECURE_NOTIFICATIONS>=0){notification=true;calculate -= KEYGUARD_DISABLE_SECURE_NOTIFICATIONS } - if(calculate-KEYGUARD_DISABLE_SECURE_CAMERA>=0){camera=true;calculate -= KEYGUARD_DISABLE_SECURE_CAMERA } - if(calculate-KEYGUARD_DISABLE_WIDGETS_ALL>=0){widgets=true;calculate -= KEYGUARD_DISABLE_WIDGETS_ALL } - } + val focusMgr = LocalFocusManager.current + var inputContent by remember{ mutableStateOf(if(isDeviceOwner(myDpm)){myDpm.getMaximumFailedPasswordsForWipe(myComponent).toString()}else{""}) } + var ableToApply by remember{ mutableStateOf(inputContent!=""&&inputContent!="0") } + 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 + ableToApply = inputContent!=""&&inputContent!="0" + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + enabled = isDeviceOwner(myDpm), + modifier = Modifier.focusable().fillMaxWidth() + ) + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = {focusMgr.clearFocus() ; myDpm.setMaximumFailedPasswordsForWipe(myComponent,inputContent.toInt())}, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.apply)) } - if(state==-1){ - state = when(myDpm.getKeyguardDisabledFeatures(myComponent)){ - KEYGUARD_DISABLE_FEATURES_NONE->0 - KEYGUARD_DISABLE_FEATURES_ALL->1 - else->2 - } - calculateCustomFeature() + } +} + +@Composable +private fun PasswordExpiration(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + var inputContent by remember{ mutableStateOf(if(isDeviceOwner(myDpm)){myDpm.getPasswordExpirationTimeout(myComponent).toString()}else{""}) } + var ableToApply by remember{ mutableStateOf(inputContent!="") } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.pwd_timeout), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text= stringResource(R.string.pwd_timeout_desc)) + Spacer(Modifier.padding(vertical = 5.dp)) + OutlinedTextField( + value = inputContent, + label = { Text(stringResource(R.string.time_unit_ms))}, + onValueChange = { + inputContent = it + ableToApply = inputContent!="" + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + enabled = isDeviceOwner(myDpm), + modifier = Modifier.focusable().fillMaxWidth() + ) + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = {focusMgr.clearFocus() ; myDpm.setPasswordExpirationTimeout(myComponent,inputContent.toLong())}, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.apply)) } - Text(text = stringResource(R.string.keyguard_disabled_features), style = typography.titleLarge) + } +} + +@Composable +private fun PasswordHistoryLength(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + var inputContent by remember{ mutableStateOf(if(isDeviceOwner(myDpm)){myDpm.getPasswordHistoryLength(myComponent).toString()}else{""}) } + var ableToApply by remember{ mutableStateOf(inputContent!="") } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.pwd_timeout), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text= stringResource(R.string.pwd_timeout_desc)) + Spacer(Modifier.padding(vertical = 5.dp)) + OutlinedTextField( + value = inputContent, + label = { Text(stringResource(R.string.time_unit_ms))}, + onValueChange = { + inputContent = it + ableToApply = inputContent!="" + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + enabled = isDeviceOwner(myDpm), + modifier = Modifier.focusable().fillMaxWidth() + ) + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = {focusMgr.clearFocus() ; myDpm.setPasswordHistoryLength(myComponent,inputContent.toInt())}, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.apply)) + } + } +} + +@Composable +private fun KeyguardDisabledFeatures(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + var state by remember{mutableIntStateOf(-1)} + var shortcuts by remember{mutableStateOf(false)} + var biometrics by remember{mutableStateOf(false)} + var iris by remember{mutableStateOf(false)} + var face by remember{mutableStateOf(false)} + var remote by remember{mutableStateOf(false)} + var fingerprint by remember{mutableStateOf(false)} + var agents by remember{mutableStateOf(false)} + var unredacted by remember{mutableStateOf(false)} + var notification by remember{mutableStateOf(false)} + var camera by remember{mutableStateOf(false)} + var widgets by remember{mutableStateOf(false)} + val calculateCustomFeature = { + var calculate = myDpm.getKeyguardDisabledFeatures(myComponent) + if(calculate==0){state=0} + else{ + if(calculate-KEYGUARD_DISABLE_SHORTCUTS_ALL>=0 && VERSION.SDK_INT>=34){shortcuts=true;calculate-= KEYGUARD_DISABLE_SHORTCUTS_ALL } + if(calculate-KEYGUARD_DISABLE_BIOMETRICS>=0&&VERSION.SDK_INT>=28){biometrics=true;calculate -= KEYGUARD_DISABLE_BIOMETRICS } + if(calculate-KEYGUARD_DISABLE_IRIS>=0&&VERSION.SDK_INT>=28){iris=true;calculate -= KEYGUARD_DISABLE_IRIS } + if(calculate-KEYGUARD_DISABLE_FACE>=0&&VERSION.SDK_INT>=28){face=true;calculate -= KEYGUARD_DISABLE_FACE } + if(calculate-KEYGUARD_DISABLE_REMOTE_INPUT>=0&&VERSION.SDK_INT>=24){remote=true;calculate -= KEYGUARD_DISABLE_REMOTE_INPUT } + if(calculate-KEYGUARD_DISABLE_FINGERPRINT>=0){fingerprint=true;calculate -= KEYGUARD_DISABLE_FINGERPRINT } + if(calculate-KEYGUARD_DISABLE_TRUST_AGENTS>=0){agents=true;calculate -= KEYGUARD_DISABLE_TRUST_AGENTS } + if(calculate-KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS>=0){unredacted=true;calculate -= KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS } + if(calculate-KEYGUARD_DISABLE_SECURE_NOTIFICATIONS>=0){notification=true;calculate -= KEYGUARD_DISABLE_SECURE_NOTIFICATIONS } + if(calculate-KEYGUARD_DISABLE_SECURE_CAMERA>=0){camera=true;calculate -= KEYGUARD_DISABLE_SECURE_CAMERA } + if(calculate-KEYGUARD_DISABLE_WIDGETS_ALL>=0){widgets=true;calculate -= KEYGUARD_DISABLE_WIDGETS_ALL } + } + } + if(state==-1){ + state = when(myDpm.getKeyguardDisabledFeatures(myComponent)){ + KEYGUARD_DISABLE_FEATURES_NONE->0 + KEYGUARD_DISABLE_FEATURES_ALL->1 + else->2 + } + calculateCustomFeature() + } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.keyguard_disabled_features), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) RadioButtonItem(stringResource(R.string.enable_all),{state==0},{state=0}) RadioButtonItem(stringResource(R.string.disable_all),{state==1},{state=1}) RadioButtonItem(stringResource(R.string.custom),{state==2},{state=2}) @@ -396,6 +534,7 @@ fun KeyguardDisabledFeatures(){ if(VERSION.SDK_INT>=34){ CheckBoxItem(stringResource(R.string.keyguard_disabled_features_shortcuts),{shortcuts},{shortcuts=!shortcuts}) } } } + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { var result = 0 @@ -423,30 +562,34 @@ fun KeyguardDisabledFeatures(){ ) { Text(text = stringResource(R.string.apply)) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -fun PasswordQuality(){ +private fun PasswordQuality(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column{ - val passwordQuality = mapOf( - PASSWORD_QUALITY_UNSPECIFIED to stringResource(R.string.password_quality_unspecified), - PASSWORD_QUALITY_SOMETHING to stringResource(R.string.password_quality_something), - PASSWORD_QUALITY_ALPHABETIC to stringResource(R.string.password_quality_alphabetic), - PASSWORD_QUALITY_NUMERIC to stringResource(R.string.password_quality_numeric), - PASSWORD_QUALITY_ALPHANUMERIC to stringResource(R.string.password_quality_alphanumeric), - PASSWORD_QUALITY_BIOMETRIC_WEAK to stringResource(R.string.password_quality_biometrics_weak), - PASSWORD_QUALITY_NUMERIC_COMPLEX to stringResource(R.string.password_quality_numeric_complex), - PASSWORD_QUALITY_COMPLEX to stringResource(R.string.custom)+"(${stringResource(R.string.unsupported)})", - ).toList() - var selectedItem by remember{ mutableIntStateOf(passwordQuality[0].first) } - if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ selectedItem=myDpm.getPasswordQuality(myComponent) } - Text(text = stringResource(R.string.required_password_quality), style = typography.titleLarge) + val passwordQuality = mapOf( + PASSWORD_QUALITY_UNSPECIFIED to stringResource(R.string.password_quality_unspecified), + PASSWORD_QUALITY_SOMETHING to stringResource(R.string.password_quality_something), + PASSWORD_QUALITY_ALPHABETIC to stringResource(R.string.password_quality_alphabetic), + PASSWORD_QUALITY_NUMERIC to stringResource(R.string.password_quality_numeric), + PASSWORD_QUALITY_ALPHANUMERIC to stringResource(R.string.password_quality_alphanumeric), + PASSWORD_QUALITY_BIOMETRIC_WEAK to stringResource(R.string.password_quality_biometrics_weak), + PASSWORD_QUALITY_NUMERIC_COMPLEX to stringResource(R.string.password_quality_numeric_complex), + PASSWORD_QUALITY_COMPLEX to stringResource(R.string.custom)+"(${stringResource(R.string.unsupported)})", + ).toList() + var selectedItem by remember{ mutableIntStateOf(passwordQuality[0].first) } + if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ selectedItem=myDpm.getPasswordQuality(myComponent) } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.required_password_quality), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.password_complexity_instead_password_quality)) if(VERSION.SDK_INT>=31){ Text(text = stringResource(R.string.password_quality_deprecated_desc), color = colorScheme.error) } + Spacer(Modifier.padding(vertical = 5.dp)) RadioButtonItem(passwordQuality[0].second,{selectedItem==passwordQuality[0].first},{selectedItem=passwordQuality[0].first}) RadioButtonItem(passwordQuality[1].second,{selectedItem==passwordQuality[1].first},{selectedItem=passwordQuality[1].first}) RadioButtonItem(passwordQuality[2].second,{selectedItem==passwordQuality[2].first},{selectedItem=passwordQuality[2].first}) @@ -455,7 +598,7 @@ fun PasswordQuality(){ RadioButtonItem(passwordQuality[5].second,{selectedItem==passwordQuality[5].first},{selectedItem=passwordQuality[5].first}) RadioButtonItem(passwordQuality[6].second,{selectedItem==passwordQuality[6].first},{selectedItem=passwordQuality[6].first}) RadioButtonItem(passwordQuality[7].second,{selectedItem==passwordQuality[7].first},{selectedItem=passwordQuality[7].first}) - Text(text = stringResource(R.string.password_ordered_desc), modifier = Modifier.padding(vertical = 3.dp)) + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { myDpm.setPasswordQuality(myComponent,selectedItem) @@ -466,13 +609,13 @@ fun PasswordQuality(){ ) { Text(stringResource(R.string.apply)) } - if(VERSION.SDK_INT<31){ - Button(onClick = {myContext.startActivity(Intent(ACTION_SET_NEW_PASSWORD))}){Text(stringResource(R.string.require_set_new_password))} - } + Spacer(Modifier.padding(vertical = 5.dp)) + Information{Text(text = stringResource(R.string.password_ordered_desc))} + Spacer(Modifier.padding(vertical = 30.dp)) } } -fun activateToken(myContext: Context){ +private fun activateToken(myContext: Context){ val desc = myContext.getString(R.string.activate_reset_password_token_here) val keyguardManager = myContext.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager val confirmIntent = keyguardManager.createConfirmDeviceCredentialIntent(myContext.getString(R.string.app_name), desc) diff --git a/app/src/main/java/com/binbin/androidowner/dpm/Permissions.kt b/app/src/main/java/com/binbin/androidowner/dpm/Permissions.kt index 45c7603..6ab7b3d 100644 --- a/app/src/main/java/com/binbin/androidowner/dpm/Permissions.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/Permissions.kt @@ -14,278 +14,284 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowRight 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.focus.FocusDirection -import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat.startActivity import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController import com.binbin.androidowner.R +import com.binbin.androidowner.ui.NavIcon +import com.binbin.androidowner.ui.SubPageItem +import com.binbin.androidowner.ui.Animations - +@OptIn(ExperimentalMaterial3Api::class) @Composable fun DpmPermissions(navCtrl:NavHostController){ + val localNavCtrl = rememberNavController() + val backStackEntry by localNavCtrl.currentBackStackEntryAsState() + val titleMap = mapOf( + "Home" to R.string.permission, + "Shizuku" to R.string.shizuku, + "DeviceAdmin" to R.string.device_admin, + "ProfileOwner" to R.string.profile_owner, + "DeviceOwner" to R.string.device_owner, + "DeviceInfo" to R.string.device_info, + "SpecificID" to R.string.enrollment_specific_id, + "OrgName" to R.string.org_name, + "NoManagementAccount" to R.string.account_types_management_disabled, + "LockScreenInfo" to R.string.owner_lockscr_info, + "SupportMsg" to R.string.support_msg, + "TransformOwnership" to R.string.transform_ownership + ) + Scaffold( + topBar = { + TopAppBar( + title = {Text(text = stringResource(titleMap[backStackEntry?.destination?.route]?:R.string.permission))}, + navigationIcon = {NavIcon{if(backStackEntry?.destination?.route=="Home"){navCtrl.navigateUp()}else{localNavCtrl.navigateUp()}}}, + colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surfaceVariant) + ) + } + ){ + NavHost( + navController = localNavCtrl, startDestination = "Home", + enterTransition = Animations().navHostEnterTransition, + exitTransition = Animations().navHostExitTransition, + popEnterTransition = Animations().navHostPopEnterTransition, + popExitTransition = Animations().navHostPopExitTransition, + modifier = Modifier + .background(color = if(isSystemInDarkTheme()) { colorScheme.background }else{ colorScheme.primary.copy(alpha = 0.05F) }) + .padding(top = it.calculateTopPadding()) + ){ + composable(route = "Home"){Home(localNavCtrl)} + composable(route = "Shizuku"){ShizukuActivate()} + composable(route = "DeviceAdmin"){DeviceAdmin()} + composable(route = "ProfileOwner"){ProfileOwner()} + composable(route = "DeviceOwner"){DeviceOwner()} + composable(route = "DeviceInfo"){DeviceInfo()} + composable(route = "SpecificID"){SpecificID()} + composable(route = "OrgName"){OrgName()} + composable(route = "NoManagementAccount"){NoManageAccount()} + composable(route = "LockScreenInfo"){LockScreenInfo()} + composable(route = "SupportMsg"){SupportMsg()} + composable(route = "TransformOwnership"){TransformOwnership()} + } + } +} + +@Composable +private fun Home(localNavCtrl:NavHostController){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val focusManager = LocalFocusManager.current - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - val titleColor = colorScheme.onPrimaryContainer - val bodyTextStyle = if(isWear){typography.bodyMedium}else{typography.bodyLarge} - val expandCommandBlock by remember{mutableStateOf("")} - Column( - modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Row( - modifier = Modifier.clickable {navCtrl.navigate("ShizukuActivate")}, - horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically - ){ - Text(text = "Shizuku", style = typography.titleLarge, color = titleColor, modifier = Modifier.padding(vertical = 2.dp)) - Icon(imageVector = Icons.Default.KeyboardArrowRight,contentDescription = null, tint = colorScheme.onPrimaryContainer) - } - DeviceAdmin() - ProfileOwner() - DeviceOwner() - if(VERSION.SDK_INT>=30){ - Column { - Text(text = stringResource(R.string.device_info), style = typography.titleLarge,color = titleColor) - if(VERSION.SDK_INT>=34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ - val financed = myDpm.isDeviceFinanced - Text(stringResource(R.string.is_device_financed, financed),style=bodyTextStyle) - } - if(VERSION.SDK_INT>=33){ - val dpmRole = myDpm.devicePolicyManagementRoleHolderPackage - Text(stringResource(R.string.dpmrh, if(dpmRole==null) { stringResource(R.string.none) } else { "" }),style=bodyTextStyle) - if(dpmRole!=null){ - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState())){ - Text(text = dpmRole, style = bodyTextStyle, color = colorScheme.onPrimaryContainer) - } - } - } - val encryptionStatus = mapOf( - DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE to stringResource(R.string.es_inactive), - DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE to stringResource(R.string.es_active), - DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED to stringResource(R.string.es_unsupported), - DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY to stringResource(R.string.es_active_default_key), - DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER to stringResource(R.string.es_active_per_user), - DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING to stringResource(R.string.unknown) - ) - Text("加密状态:${encryptionStatus[myDpm.storageEncryptionStatus]}",style=bodyTextStyle) - val adminList = myDpm.activeAdmins - if(adminList!=null){ - var adminListText = "" - Text(text = stringResource(R.string.activated_device_admin, adminList.size), style = bodyTextStyle) - var count = adminList.size - for(each in adminList){ - count -= 1 - adminListText += "$each" - if(count>0){adminListText += "\n"} - } - SelectionContainer(modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp).horizontalScroll(rememberScrollState())){ - Text(text = adminListText, style = bodyTextStyle, color = titleColor) - } - } - } - } - + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + Spacer(Modifier.padding(vertical = 10.dp)) + SubPageItem( + R.string.device_admin, stringResource(if(myDpm.isAdminActive(myComponent)){R.string.activated}else{R.string.deactivated}), + operation = {localNavCtrl.navigate("DeviceAdmin")} + ) + SubPageItem( + R.string.profile_owner, stringResource(if(isProfileOwner(myDpm)){R.string.activated}else{R.string.deactivated}), + operation = {localNavCtrl.navigate("ProfileOwner")} + ) + SubPageItem( + R.string.device_owner, stringResource(if(isDeviceOwner(myDpm)){R.string.activated}else{R.string.deactivated}), + operation = {localNavCtrl.navigate("DeviceOwner")} + ) + SubPageItem(R.string.shizuku,""){localNavCtrl.navigate("Shizuku")} + SubPageItem(R.string.device_info,""){localNavCtrl.navigate("DeviceInfo")} if(VERSION.SDK_INT>=31&&(isProfileOwner(myDpm)|| isDeviceOwner(myDpm))){ - SpecificID() + SubPageItem(R.string.enrollment_specific_id,""){localNavCtrl.navigate("SpecificID")} } - if((VERSION.SDK_INT>=26&&isDeviceOwner(myDpm))||(VERSION.SDK_INT>=24&&isProfileOwner(myDpm))){ - OrgName() + SubPageItem(R.string.org_name,""){localNavCtrl.navigate("OrgName")} } - if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){ - NoManageAccount() + SubPageItem(R.string.account_types_management_disabled,""){localNavCtrl.navigate("NoManagementAccount")} } - if(VERSION.SDK_INT>=24&&isDeviceOwner(myDpm)){ - DeviceOwnerInfo(R.string.owner_lockscr_info,R.string.place_holder,R.string.owner_lockscr_info,focusManager,myContext, - {myDpm.deviceOwnerLockScreenInfo},{content -> myDpm.setDeviceOwnerLockScreenInfo(myComponent,content)}) + SubPageItem(R.string.owner_lockscr_info,""){localNavCtrl.navigate("LockScreenInfo")} } if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - SupportMsg() + SubPageItem(R.string.support_msg,""){localNavCtrl.navigate("SupportMsg")} } - if(VERSION.SDK_INT>=28&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - TransformOwnership() + SubPageItem(R.string.transform_ownership,""){localNavCtrl.navigate("TransformOwnership")} } - Spacer(Modifier.padding(vertical = 30.dp)) } } +@SuppressLint("NewApi") @Composable -fun DeviceOwnerInfo( - name:Int, - desc:Int, - textfield:Int, - fm:FocusManager, - myContext:Context, - input:()->CharSequence?, - output:(content:String?)->Unit -){ - Column{ - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - Text(text = stringResource(name), style = typography.titleLarge, softWrap = false, color = colorScheme.onPrimaryContainer) - if(desc!=R.string.place_holder){ - Text( - text = stringResource(desc),modifier = Modifier.padding(top = 6.dp), - style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}) - } - var inputContent by remember{ mutableStateOf(input()) } +private fun LockScreenInfo(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + var infoText by remember{mutableStateOf(myDpm.deviceOwnerLockScreenInfo?.toString() ?: "")} + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){ + Text(text = stringResource(R.string.owner_lockscr_info), style = typography.headlineLarge) OutlinedTextField( - value = if(inputContent!=null){ inputContent.toString() }else{""}, - label = {Text(stringResource(textfield))}, - onValueChange = { inputContent=it }, + value = infoText, + label = {Text(stringResource(R.string.owner_lockscr_info))}, + onValueChange = { infoText=it }, modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp) ) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - output(inputContent.toString()) - inputContent= input() - fm.clearFocus() - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = if(isWear){Modifier.fillMaxWidth(0.49F)}else{Modifier.fillMaxWidth(0.6F)} - ) { - Text(text = stringResource(R.string.apply)) - } - Button( - onClick = { - output(null) - inputContent = input() - fm.clearFocus() - Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(text = stringResource(R.string.reset)) - } + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setDeviceOwnerLockScreenInfo(myComponent,infoText) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.apply)) + } + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setDeviceOwnerLockScreenInfo(myComponent,null) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.reset)) } } } @Composable -fun DeviceAdmin(){ +private fun DeviceAdmin(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column { - Row( - horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text(text = "Device Admin") - Text( - text = stringResource( - if(myDpm.isAdminActive(myComponent)) { - R.string.activated - } else { - R.string.deactivated - } - ) - ) + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.device_admin), style = typography.headlineLarge) + Text(text = stringResource(if(myDpm.isAdminActive(myComponent)) { R.string.activated } else { R.string.deactivated }), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + if(myDpm.isAdminActive(myComponent)) { + if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)) { + Button(onClick = {myDpm.removeActiveAdmin(myComponent)}, modifier = Modifier.fillMaxWidth()) { + Text(stringResource(R.string.deactivate)) + } } - if(myDpm.isAdminActive(myComponent)) { - if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)) { - Button(onClick = { - myDpm.removeActiveAdmin(myComponent) - }) { - Text(stringResource(R.string.deactivate)) - } - } - } else { - Button(onClick = {activateDeviceAdmin(myContext, myComponent)}) { - Text(stringResource(R.string.activate)) - } + } else { + Button(onClick = {activateDeviceAdmin(myContext, myComponent)}, modifier = Modifier.fillMaxWidth()) { + Text(stringResource(R.string.activate)) } } + Spacer(Modifier.padding(vertical = 5.dp)) if(!myDpm.isAdminActive(myComponent)) { SelectionContainer { - Text(text = stringResource(R.string.activate_device_admin_command), color = colorScheme.onTertiaryContainer) + Text(text = stringResource(R.string.activate_device_admin_command)) } } } } @Composable -fun ProfileOwner(){ +private fun ProfileOwner(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column { - if(!isDeviceOwner(myDpm)){ - Row( - - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text(text = "Profile Owner") - Text(stringResource(if(isProfileOwner(myDpm)){R.string.activated}else{R.string.deactivated})) - } - if(isProfileOwner(myDpm)&&VERSION.SDK_INT>=24&&!myDpm.isManagedProfile(myComponent)){ - Button(onClick = {myDpm.clearProfileOwner(myComponent)}) { - Text(stringResource(R.string.deactivate)) - } - } + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.profile_owner), style = typography.headlineLarge) + Text(stringResource(if(isProfileOwner(myDpm)){R.string.activated}else{R.string.deactivated}), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + if(isProfileOwner(myDpm)&&VERSION.SDK_INT>=24){ + Button(onClick = {myDpm.clearProfileOwner(myComponent)}, modifier = Modifier.fillMaxWidth()) { + Text(stringResource(R.string.deactivate)) } } - if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ + if(!isProfileOwner(myDpm)){ SelectionContainer{ - Text(text = stringResource(R.string.activate_profile_owner_command), color = colorScheme.onTertiaryContainer) + Text(text = stringResource(R.string.activate_profile_owner_command)) } } } } @Composable -fun DeviceOwner(){ +private fun DeviceOwner(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - Column { - if(!isProfileOwner(myDpm)){ - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column { - Text(text = "Device Owner") - Text(stringResource(if(isDeviceOwner(myDpm)){R.string.activated}else{R.string.deactivated})) - } - if(isDeviceOwner(myDpm)){ - Button( - onClick = { - myDpm.clearDeviceOwnerApp(myContext.packageName) - } - ) { - Text(stringResource(R.string.deactivate)) - } + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.device_owner), style = typography.headlineLarge) + Text(text = stringResource(if(isDeviceOwner(myDpm)){R.string.activated}else{R.string.deactivated}), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + if(isDeviceOwner(myDpm)){ + Button(onClick = {myDpm.clearDeviceOwnerApp(myContext.packageName)}, modifier = Modifier.fillMaxWidth()) { + Text(text = stringResource(R.string.deactivate)) + } + } + if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ + SelectionContainer{ + Text(text = stringResource(R.string.activate_device_owner_command)) + } + } + } +} + +@Composable +fun DeviceInfo(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.device_info), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + if(VERSION.SDK_INT>=34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ + val financed = myDpm.isDeviceFinanced + Text(stringResource(R.string.is_device_financed, financed)) + } + Spacer(Modifier.padding(vertical = 2.dp)) + if(VERSION.SDK_INT>=33){ + val dpmRole = myDpm.devicePolicyManagementRoleHolderPackage + Text(stringResource(R.string.dpmrh, if(dpmRole==null) { stringResource(R.string.none) } else { "" })) + if(dpmRole!=null){ + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState())){ + Text(text = dpmRole) } } } - - if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){ - SelectionContainer{ - Text(text = stringResource(R.string.activate_device_owner_command), color = colorScheme.onTertiaryContainer) + Spacer(Modifier.padding(vertical = 2.dp)) + val encryptionStatus = mutableMapOf( + DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE to stringResource(R.string.es_inactive), + DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE to stringResource(R.string.es_active), + DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED to stringResource(R.string.es_unsupported), + DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING to stringResource(R.string.unknown) + ) + if(VERSION.SDK_INT>=23){ encryptionStatus[DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY] = stringResource(R.string.es_active_default_key) } + if(VERSION.SDK_INT>=24){ encryptionStatus[DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER] = stringResource(R.string.es_active_per_user) } + Text("加密状态:${encryptionStatus[myDpm.storageEncryptionStatus]}") + Spacer(Modifier.padding(vertical = 2.dp)) + val adminList = myDpm.activeAdmins + if(adminList!=null){ + var adminListText = "" + Text(text = stringResource(R.string.activated_device_admin, adminList.size)) + var count = adminList.size + for(each in adminList){ + count -= 1 + adminListText += "$each" + if(count>0){adminListText += "\n"} + } + SelectionContainer(modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp).horizontalScroll(rememberScrollState())){ + Text(text = adminListText) } } } @@ -293,14 +299,16 @@ fun DeviceOwner(){ @SuppressLint("NewApi") @Composable -fun SpecificID(){ +private fun SpecificID(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - Column{ + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){ val specificId = myDpm.enrollmentSpecificId - Text(text = stringResource(R.string.enrollment_specific_id), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.enrollment_specific_id), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) if(specificId!=""){ - SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState())){ Text(specificId,softWrap = false) } + SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState())){ Text(specificId) } }else{ Text(stringResource(R.string.require_set_org_id)) } @@ -309,20 +317,23 @@ fun SpecificID(){ @SuppressLint("NewApi") @Composable -fun OrgName(){ +private fun OrgName(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){ var orgName by remember{mutableStateOf(try{myDpm.getOrganizationName(myComponent).toString()}catch(e:SecurityException){""})} - Text(text = stringResource(R.string.org_name), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.org_name), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) OutlinedTextField( value = orgName, onValueChange = {orgName=it}, modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp), label = {Text(stringResource(R.string.org_name))}, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) ) + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { focusMgr.clearFocus() @@ -338,94 +349,150 @@ fun OrgName(){ @SuppressLint("NewApi") @Composable -fun SupportMsg(){ +private fun SupportMsg(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - DeviceOwnerInfo(R.string.support_msg,R.string.support_msg_desc,R.string.message,focusMgr,myContext, - {myDpm.getShortSupportMessage(myComponent)},{content -> myDpm.setShortSupportMessage(myComponent,content)}) - DeviceOwnerInfo(R.string.long_support_msg,R.string.long_support_msg_desc,R.string.message,focusMgr,myContext, - {myDpm.getLongSupportMessage(myComponent)},{content -> myDpm.setLongSupportMessage(myComponent,content)}) + var shortMsg by remember{mutableStateOf(myDpm.getShortSupportMessage(myComponent)?.toString() ?: "")} + var longMsg by remember{mutableStateOf(myDpm.getLongSupportMessage(myComponent)?.toString() ?: "")} + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.short_support_msg), style = typography.headlineLarge) + OutlinedTextField( + value = shortMsg, + label = {Text(stringResource(R.string.message))}, + onValueChange = { shortMsg=it }, + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp) + ) + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setShortSupportMessage(myComponent,shortMsg) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.apply)) + } + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setShortSupportMessage(myComponent,null) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.reset)) + } + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.long_support_msg), style = typography.headlineLarge) + OutlinedTextField( + value = longMsg, + label = {Text(stringResource(R.string.message))}, + onValueChange = { longMsg=it }, + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp) + ) + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setLongSupportMessage(myComponent,longMsg) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.apply)) + } + Button( + onClick = { + focusMgr.clearFocus() + myDpm.setLongSupportMessage(myComponent,null) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.reset)) + } + Spacer(Modifier.padding(vertical = 30.dp)) + } } @Composable -fun NoManageAccount(){ +private fun NoManageAccount(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ - Text(text = stringResource(R.string.account_types_management_disabled), style = typography.titleLarge) - Text(stringResource(R.string.developing)) - var noManageAccount = myDpm.accountTypesWithManagementDisabled - var accountlist by remember{ mutableStateOf("") } + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.account_types_management_disabled), style = typography.headlineLarge) + Text(stringResource(R.string.unknown_effect)) + var accountList by remember{ mutableStateOf("") } val refreshList = { - accountlist = "" + val noManageAccount = myDpm.accountTypesWithManagementDisabled + accountList = "" if (noManageAccount != null) { - var count = noManageAccount!!.size - for(each in noManageAccount!!){ count -= 1; accountlist += each; if(count>0){accountlist += "\n"} } + 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 } - Text(text = if(accountlist==""){stringResource(R.string.none)}else{accountlist}) + 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_types))}, - modifier = Modifier.focusable().fillMaxWidth().padding(bottom = 4.dp), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) ) - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick={ - focusMgr.clearFocus() - myDpm.setAccountManagementDisabled(myComponent,inputText,true) - noManageAccount=myDpm.accountTypesWithManagementDisabled - refreshList() - }, - modifier = Modifier.fillMaxWidth(0.49f) - ){ - Text(stringResource(R.string.add)) - } - Button( - onClick={focusMgr.clearFocus() - myDpm.setAccountManagementDisabled(myComponent,inputText,false) - noManageAccount=myDpm.accountTypesWithManagementDisabled - refreshList() - }, - modifier = Modifier.fillMaxWidth(0.96F) - ){ - Text(stringResource(R.string.remove)) - } + Button( + onClick={ + myDpm.setAccountManagementDisabled(myComponent,inputText,true) + refreshList() + }, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.add)) + } + Button( + onClick={ + myDpm.setAccountManagementDisabled(myComponent,inputText,false) + refreshList() + }, + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.remove)) } } } @SuppressLint("NewApi") @Composable -fun TransformOwnership(){ +private fun TransformOwnership(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){ var pkg by remember{mutableStateOf("")} var cls by remember{mutableStateOf("")} - Text(text = stringResource(R.string.transform_ownership), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.transform_ownership), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.transform_ownership_desc)) OutlinedTextField( value = pkg, onValueChange = {pkg = it}, label = {Text(stringResource(R.string.target_package_name))}, - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), keyboardActions = KeyboardActions(onNext = {focusMgr.moveFocus(FocusDirection.Down)}) ) OutlinedTextField( value = cls, onValueChange = {cls = it}, label = {Text(stringResource(R.string.target_class_name))}, - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp), + modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) ) @@ -442,10 +509,11 @@ fun TransformOwnership(){ ) { Text(stringResource(R.string.transform)) } + Spacer(Modifier.padding(vertical = 30.dp)) } } -fun activateDeviceAdmin(inputContext:Context,inputComponent:ComponentName){ +private fun activateDeviceAdmin(inputContext:Context,inputComponent:ComponentName){ try { val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN) intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, inputComponent) diff --git a/app/src/main/java/com/binbin/androidowner/dpm/SystemManage.kt b/app/src/main/java/com/binbin/androidowner/dpm/SystemManage.kt index 9843112..d4a11ce 100644 --- a/app/src/main/java/com/binbin/androidowner/dpm/SystemManage.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/SystemManage.kt @@ -5,6 +5,9 @@ import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager.* import android.app.admin.SystemUpdateInfo import android.app.admin.SystemUpdatePolicy +import android.app.admin.SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC +import android.app.admin.SystemUpdatePolicy.TYPE_INSTALL_WINDOWED +import android.app.admin.SystemUpdatePolicy.TYPE_POSTPONE import android.content.ComponentName import android.content.Context import android.content.Intent @@ -15,239 +18,238 @@ import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.focusable +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState 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.* 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.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController import com.binbin.androidowner.R -import com.binbin.androidowner.sections -import com.binbin.androidowner.ui.CheckBoxItem -import com.binbin.androidowner.ui.RadioButtonItem +import com.binbin.androidowner.toText +import com.binbin.androidowner.ui.* +import com.binbin.androidowner.ui.Animations import kotlinx.coroutines.delay import java.util.Date +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun SystemManage(){ +fun SystemManage(navCtrl:NavHostController){ + val localNavCtrl = rememberNavController() + val backStackEntry by localNavCtrl.currentBackStackEntryAsState() + val titleMap = mapOf( + "Switches" to R.string.options, + "Keyguard" to R.string.keyguard, + "BugReport" to R.string.request_bug_report, + "Reboot" to R.string.reboot, + "EditTime" to R.string.edit_time, + "PermissionPolicy" to R.string.permission_policy, + "MTEPolicy" to R.string.mte_policy, + "NearbyStreamingPolicy" to R.string.nearby_streaming_policy, + "LockTaskFeatures" to R.string.lock_task_feature, + "CaCert" to R.string.ca_cert, + "SecurityLogs" to R.string.security_logs, + "SystemUpdatePolicy" to R.string.system_update_policy, + "WipeData" to R.string.wipe_data + ) + Scaffold( + topBar = { + TopAppBar( + title = {Text(text = stringResource(titleMap[backStackEntry?.destination?.route]?:R.string.device_ctrl))}, + navigationIcon = {NavIcon{if(backStackEntry?.destination?.route=="Home"){navCtrl.navigateUp()}else{localNavCtrl.navigateUp()}}}, + colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surfaceVariant) + ) + } + ){ + NavHost( + navController = localNavCtrl, startDestination = "Home", + enterTransition = Animations().navHostEnterTransition, + exitTransition = Animations().navHostExitTransition, + popEnterTransition = Animations().navHostPopEnterTransition, + popExitTransition = Animations().navHostPopExitTransition, + modifier = Modifier + .background(color = if(isSystemInDarkTheme()) { colorScheme.background }else{ colorScheme.primary.copy(alpha = 0.05F) }) + .padding(top = it.calculateTopPadding()) + ){ + composable(route = "Home"){Home(localNavCtrl)} + composable(route = "Switches"){Switches()} + composable(route = "Keyguard"){Keyguard()} + composable(route = "BugReport"){BugReport()} + composable(route = "Reboot"){Reboot()} + composable(route = "EditTime"){EditTime()} + composable(route = "PermissionPolicy"){PermissionPolicy()} + composable(route = "MTEPolicy"){MTEPolicy()} + composable(route = "NearbyStreamingPolicy"){NearbyStreamingPolicy()} + composable(route = "LockTaskFeatures"){LockTaskFeatures()} + composable(route = "CaCert"){CaCert()} + composable(route = "SecurityLogs"){SecurityLogs()} + composable(route = "SystemUpdatePolicy"){SysUpdatePolicy()} + composable(route = "WipeData"){WipeData()} + } + } +} + +@Composable +private fun Home(navCtrl: NavHostController){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - DeviceCtrlItem(R.string.disable_cam,R.string.place_holder, R.drawable.photo_camera_fill0, - {myDpm.getCameraDisabled(null)},{b -> myDpm.setCameraDisabled(myComponent,b)} - ) + SubPageItem(R.string.options,""){navCtrl.navigate("Switches")} } - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - DeviceCtrlItem(R.string.disable_scrcap,R.string.aosp_scrrec_also_work,R.drawable.screenshot_fill0, - {myDpm.getScreenCaptureDisabled(null)},{b -> myDpm.setScreenCaptureDisabled(myComponent,b) } - ) - } - if(VERSION.SDK_INT>=34&&(isDeviceOwner(myDpm)|| (isProfileOwner(myDpm)&&myDpm.isAffiliatedUser))){ - DeviceCtrlItem(R.string.disable_status_bar,R.string.place_holder,R.drawable.notifications_fill0, - {myDpm.isStatusBarDisabled},{b -> myDpm.setStatusBarDisabled(myComponent,b) } - ) - } - if(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile)){ - if(VERSION.SDK_INT>=30){ - DeviceCtrlItem(R.string.auto_time,R.string.place_holder,R.drawable.schedule_fill0, - {myDpm.getAutoTimeEnabled(myComponent)},{b -> myDpm.setAutoTimeEnabled(myComponent,b) } - ) - DeviceCtrlItem(R.string.auto_timezone,R.string.place_holder,R.drawable.globe_fill0, - {myDpm.getAutoTimeZoneEnabled(myComponent)},{b -> myDpm.setAutoTimeZoneEnabled(myComponent,b) } - ) - }else{ - DeviceCtrlItem(R.string.auto_time,R.string.place_holder,R.drawable.schedule_fill0,{myDpm.autoTimeRequired},{b -> myDpm.setAutoTimeRequired(myComponent,b)}) - } - } - if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ - DeviceCtrlItem(R.string.master_mute,R.string.place_holder,R.drawable.volume_up_fill0, - {myDpm.isMasterVolumeMuted(myComponent)},{b -> myDpm.setMasterVolumeMuted(myComponent,b) } - ) - } - if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)|| isProfileOwner(myDpm))){ - DeviceCtrlItem(R.string.backup_service,R.string.place_holder,R.drawable.backup_fill0, - {myDpm.isBackupServiceEnabled(myComponent)},{b -> myDpm.setBackupServiceEnabled(myComponent,b) } - ) - } - if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)|| isProfileOwner(myDpm))){ - DeviceCtrlItem(R.string.disable_bt_contact_share,R.string.place_holder,R.drawable.account_circle_fill0, - {myDpm.getBluetoothContactSharingDisabled(myComponent)},{b -> myDpm.setBluetoothContactSharingDisabled(myComponent,b)} - ) - } - if(VERSION.SDK_INT>=30&&isDeviceOwner(myDpm)){ - DeviceCtrlItem(R.string.common_criteria_mode,R.string.common_criteria_mode_desc,R.drawable.security_fill0, - {myDpm.isCommonCriteriaModeEnabled(myComponent)},{b -> myDpm.setCommonCriteriaModeEnabled(myComponent,b)} - ) - } - if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ - if(myDpm.canUsbDataSignalingBeDisabled()){ - DeviceCtrlItem(R.string.usb_signal,R.string.place_holder,R.drawable.usb_fill0, - {myDpm.isUsbDataSignalingEnabled},{b -> myDpm.isUsbDataSignalingEnabled = b } - ) - }else{ - Text(text = stringResource(R.string.turn_off_usb_not_support),modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) - } - } - - Keyguard() - + SubPageItem(R.string.keyguard,""){navCtrl.navigate("Keyguard")} if(VERSION.SDK_INT>=24){ - Column{ - Button( - onClick = { - val result = myDpm.requestBugreport(myComponent) - Toast.makeText(myContext, if(result){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() - }, - modifier = Modifier.fillMaxWidth(), - enabled = isDeviceOwner(myDpm) - ) { - Text(stringResource(R.string.request_bug_report)) - } - Button( - onClick = {myDpm.reboot(myComponent)}, - enabled = isDeviceOwner(myDpm), - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.reboot)) - } - } + SubPageItem(R.string.request_bug_report,""){navCtrl.navigate("BugReport")} + SubPageItem(R.string.reboot,""){navCtrl.navigate("Reboot")} } - if(VERSION.SDK_INT>=28){ - EditTime() + SubPageItem(R.string.edit_time,""){navCtrl.navigate("EditTime")} } - if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - PermissionPolicy() + SubPageItem(R.string.permission_policy,""){navCtrl.navigate("PermissionPolicy")} } - if(VERSION.SDK_INT>=34&&isDeviceOwner(myDpm)){ - MTEPolicy() + SubPageItem(R.string.mte_policy,""){navCtrl.navigate("MTEPolicy")} } - if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - NearbyStreamingPolicy() + SubPageItem(R.string.nearby_streaming_policy,""){navCtrl.navigate("NearbyStreamingPolicy")} } - if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ - LockTaskFeatures() + SubPageItem(R.string.lock_task_feature,""){navCtrl.navigate("LockTaskFeatures")} } - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - CaCert() + SubPageItem(R.string.ca_cert,""){navCtrl.navigate("CaCert")} } - if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ - SecurityLogs() + SubPageItem(R.string.security_logs,""){navCtrl.navigate("SecurityLogs")} } - - if(isDeviceOwner(myDpm)){ - SysUpdatePolicy() + if(VERSION.SDK_INT>=23&&isDeviceOwner(myDpm)){ + SubPageItem(R.string.system_update_policy,""){navCtrl.navigate("SystemUpdatePolicy")} } - - WipeData() - + SubPageItem(R.string.wipe_data,""){navCtrl.navigate("WipeData")} Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -fun DeviceCtrlItem( - itemName:Int, - itemDesc:Int, - leadIcon:Int, - getMethod:()->Boolean, - setMethod:(b:Boolean)->Unit -){ - var isEnabled by remember{ mutableStateOf(false) } - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = if(isWear){Modifier.fillMaxWidth(0.65F)}else{Modifier.fillMaxWidth(0.8F)} - ){ - if(!isWear){ - Icon( - painter = painterResource(leadIcon), - contentDescription = null, - tint = colorScheme.onPrimaryContainer, - modifier = Modifier.padding(start = 5.dp, end = 9.dp) - )} - Column { - Text( - text = stringResource(itemName), - style = if(!isWear){typography.titleLarge}else{typography.titleMedium}, - color = colorScheme.onPrimaryContainer, - fontWeight = if(isWear){ FontWeight.SemiBold }else{ FontWeight.Medium } +private fun Switches(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + SwitchItem(R.string.disable_cam,"", R.drawable.photo_camera_fill0, + {myDpm.getCameraDisabled(null)},{myDpm.setCameraDisabled(myComponent,it)} + ) + } + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + SwitchItem(R.string.disable_scrcap, stringResource(R.string.aosp_scrrec_also_work),R.drawable.screenshot_fill0, + {myDpm.getScreenCaptureDisabled(null)},{myDpm.setScreenCaptureDisabled(myComponent,it) } + ) + } + if(VERSION.SDK_INT>=34&&(isDeviceOwner(myDpm)|| (isProfileOwner(myDpm)&&myDpm.isAffiliatedUser))){ + SwitchItem(R.string.disable_status_bar,"",R.drawable.notifications_fill0, + {myDpm.isStatusBarDisabled},{myDpm.setStatusBarDisabled(myComponent,it) } + ) + } + if(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile)){ + if(VERSION.SDK_INT>=30){ + SwitchItem(R.string.auto_time,"",R.drawable.schedule_fill0, + {myDpm.getAutoTimeEnabled(myComponent)},{myDpm.setAutoTimeEnabled(myComponent,it) } ) - if(itemDesc!=R.string.place_holder){ Text(stringResource(itemDesc)) } + SwitchItem(R.string.auto_timezone,"",R.drawable.globe_fill0, + {myDpm.getAutoTimeZoneEnabled(myComponent)},{myDpm.setAutoTimeZoneEnabled(myComponent,it) } + ) + }else{ + SwitchItem(R.string.auto_time,"",R.drawable.schedule_fill0,{myDpm.autoTimeRequired},{myDpm.setAutoTimeRequired(myComponent,it)}) } } - isEnabled = getMethod() - Switch( - checked = isEnabled, - onCheckedChange = { - setMethod(!isEnabled) - isEnabled=getMethod() - }, - modifier = Modifier.padding(end = 5.dp) - ) + if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ + SwitchItem(R.string.master_mute,"",R.drawable.volume_up_fill0, + {myDpm.isMasterVolumeMuted(myComponent)},{myDpm.setMasterVolumeMuted(myComponent,it) } + ) + } + if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)|| isProfileOwner(myDpm))){ + SwitchItem(R.string.backup_service,"",R.drawable.backup_fill0, + {myDpm.isBackupServiceEnabled(myComponent)},{myDpm.setBackupServiceEnabled(myComponent,it) } + ) + } + if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)|| isProfileOwner(myDpm))){ + SwitchItem(R.string.disable_bt_contact_share,"",R.drawable.account_circle_fill0, + {myDpm.getBluetoothContactSharingDisabled(myComponent)},{myDpm.setBluetoothContactSharingDisabled(myComponent,it)} + ) + } + if(VERSION.SDK_INT>=30&&isDeviceOwner(myDpm)){ + SwitchItem(R.string.common_criteria_mode, stringResource(R.string.common_criteria_mode_desc),R.drawable.security_fill0, + {myDpm.isCommonCriteriaModeEnabled(myComponent)},{myDpm.setCommonCriteriaModeEnabled(myComponent,it)} + ) + } + if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){ + SwitchItem( + R.string.usb_signal,"",R.drawable.usb_fill0, {myDpm.isUsbDataSignalingEnabled}, + { + if(myDpm.canUsbDataSignalingBeDisabled()){ + myDpm.isUsbDataSignalingEnabled = it + }else{ + Toast.makeText(myContext,myContext.getString(R.string.unsupported),Toast.LENGTH_SHORT).show() + } + } + ) + } + Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -fun Keyguard(){ +private fun Keyguard(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column{ - Text(text = stringResource(R.string.keyguard), style = typography.titleLarge,color = colorScheme.onPrimaryContainer) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.keyguard), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) if(VERSION.SDK_INT>=23){ - Text(text = stringResource(R.string.require_no_password_to_disable)) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - Toast.makeText(myContext, - myContext.getString(if(myDpm.setKeyguardDisabled(myComponent,true)){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm)|| (VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isAffiliatedUser), - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text(stringResource(R.string.disable)) - } - Button( - onClick = { - Toast.makeText(myContext, - myContext.getString(if(myDpm.setKeyguardDisabled(myComponent,false)){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm)|| (VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isAffiliatedUser), - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text(stringResource(R.string.enable)) - } + Button( + onClick = { + Toast.makeText(myContext, + myContext.getString(if(myDpm.setKeyguardDisabled(myComponent,true)){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)|| (VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isAffiliatedUser), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.disable)) } + Button( + onClick = { + Toast.makeText(myContext, + myContext.getString(if(myDpm.setKeyguardDisabled(myComponent,false)){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)|| (VERSION.SDK_INT>=28&&isProfileOwner(myDpm)&&myDpm.isAffiliatedUser), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.enable)) + } + Spacer(Modifier.padding(vertical = 3.dp)) + Information{Text(text = stringResource(R.string.require_no_password_to_disable))} + Spacer(Modifier.padding(vertical = 8.dp)) } var flag by remember{mutableIntStateOf(0)} Button( @@ -258,20 +260,63 @@ fun Keyguard(){ Text(stringResource(R.string.lock_now)) } if(VERSION.SDK_INT>=26){ CheckBoxItem(stringResource(R.string.require_enter_password_again),{flag==FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY},{flag = if(flag==0){1}else{0} }) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun EditTime(){ +private fun BugReport(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp)){ + Spacer(Modifier.padding(vertical = 10.dp)) + Button( + onClick = { + val result = myDpm.requestBugreport(myComponent) + Toast.makeText(myContext, if(result){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth(), + enabled = isDeviceOwner(myDpm) + ) { + Text(stringResource(R.string.request_bug_report)) + } + } +} + +@SuppressLint("NewApi") +@Composable +private fun Reboot(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp)){ + Spacer(Modifier.padding(vertical = 10.dp)) + Button( + onClick = {myDpm.reboot(myComponent)}, + enabled = isDeviceOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.reboot)) + } + } +} + +@SuppressLint("NewApi") +@Composable +private fun EditTime(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ - Text(text = stringResource(R.string.edit_time), style = typography.titleLarge) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.edit_time), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) var inputTime by remember{mutableStateOf("")} Text(text = stringResource(R.string.from_epoch_to_target_time)) + Spacer(Modifier.padding(vertical = 5.dp)) OutlinedTextField( value = inputTime, label = { Text(stringResource(R.string.time_unit_ms))}, @@ -279,38 +324,40 @@ fun EditTime(){ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), enabled = isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp) + modifier = Modifier.focusable().fillMaxWidth() ) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ - Button( - onClick = {myDpm.setTime(myComponent,inputTime.toLong())}, - modifier = Modifier.fillMaxWidth(0.35F), - enabled = inputTime!=""&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile)) - ) { - Text("应用") - } - Button( - onClick = {inputTime = System.currentTimeMillis().toString()}, - modifier = Modifier.fillMaxWidth(0.98F) - ) { - Text(stringResource(R.string.get_current_time)) - } + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = {myDpm.setTime(myComponent,inputTime.toLong())}, + modifier = Modifier.fillMaxWidth(), + enabled = inputTime!=""&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile)) + ) { + Text("应用") + } + Button( + onClick = {inputTime = System.currentTimeMillis().toString()}, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.get_current_time)) } } } @SuppressLint("NewApi") @Composable -fun PermissionPolicy(){ +private fun PermissionPolicy(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ var selectedPolicy by remember{mutableIntStateOf(myDpm.getPermissionPolicy(myComponent))} - Text(text = stringResource(R.string.permission_policy), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.permission_policy), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) RadioButtonItem(stringResource(R.string.default_stringres), {selectedPolicy==PERMISSION_POLICY_PROMPT}, {selectedPolicy= PERMISSION_POLICY_PROMPT}) RadioButtonItem(stringResource(R.string.auto_grant), {selectedPolicy==PERMISSION_POLICY_AUTO_GRANT}, {selectedPolicy= PERMISSION_POLICY_AUTO_GRANT}) RadioButtonItem(stringResource(R.string.auto_deny), {selectedPolicy==PERMISSION_POLICY_AUTO_DENY}, {selectedPolicy= PERMISSION_POLICY_AUTO_DENY}) + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { myDpm.setPermissionPolicy(myComponent,selectedPolicy) @@ -325,12 +372,13 @@ fun PermissionPolicy(){ @SuppressLint("NewApi") @Composable -fun MTEPolicy(){ +private fun MTEPolicy(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - Column{ - Text(text = stringResource(R.string.mte_policy), style = typography.titleLarge) - Text(stringResource(R.string.mte_policy_desc)) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.mte_policy), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) var selectedMtePolicy by remember{mutableIntStateOf(myDpm.mtePolicy)} RadioButtonItem(stringResource(R.string.decide_by_user), {selectedMtePolicy==MTE_NOT_CONTROLLED_BY_POLICY}, {selectedMtePolicy= MTE_NOT_CONTROLLED_BY_POLICY}) RadioButtonItem(stringResource(R.string.enabled), {selectedMtePolicy==MTE_ENABLED}, {selectedMtePolicy=MTE_ENABLED}) @@ -349,21 +397,27 @@ fun MTEPolicy(){ ) { Text(stringResource(R.string.apply)) } + Spacer(Modifier.padding(vertical = 5.dp)) + Information{Text(stringResource(R.string.mte_policy_desc))} + Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun NearbyStreamingPolicy(){ +private fun NearbyStreamingPolicy(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ var appPolicy by remember{mutableIntStateOf(myDpm.nearbyAppStreamingPolicy)} + Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.nearby_app_streaming), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 3.dp)) RadioButtonItem(stringResource(R.string.decide_by_user),{appPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY},{appPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY}) RadioButtonItem(stringResource(R.string.enabled),{appPolicy == NEARBY_STREAMING_ENABLED},{appPolicy = NEARBY_STREAMING_ENABLED}) RadioButtonItem(stringResource(R.string.disabled),{appPolicy == NEARBY_STREAMING_DISABLED},{appPolicy = NEARBY_STREAMING_DISABLED}) RadioButtonItem(stringResource(R.string.enable_if_secure_enough),{appPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY},{appPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY}) + Spacer(Modifier.padding(vertical = 3.dp)) Button( onClick = { myDpm.nearbyAppStreamingPolicy = appPolicy @@ -373,13 +427,15 @@ fun NearbyStreamingPolicy(){ ) { Text("应用") } - Spacer(Modifier.padding(vertical = 3.dp)) var notificationPolicy by remember{mutableIntStateOf(myDpm.nearbyNotificationStreamingPolicy)} + Spacer(Modifier.padding(vertical = 10.dp)) Text(text = stringResource(R.string.nearby_notifi_streaming), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 3.dp)) RadioButtonItem(stringResource(R.string.decide_by_user),{notificationPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY},{notificationPolicy = NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY}) RadioButtonItem(stringResource(R.string.enabled),{notificationPolicy == NEARBY_STREAMING_ENABLED},{notificationPolicy = NEARBY_STREAMING_ENABLED}) RadioButtonItem(stringResource(R.string.disabled),{notificationPolicy == NEARBY_STREAMING_DISABLED},{notificationPolicy = NEARBY_STREAMING_DISABLED}) RadioButtonItem(stringResource(R.string.enable_if_secure_enough),{notificationPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY},{notificationPolicy = NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY}) + Spacer(Modifier.padding(vertical = 3.dp)) Button( onClick = { myDpm.nearbyNotificationStreamingPolicy = notificationPolicy @@ -389,17 +445,18 @@ fun NearbyStreamingPolicy(){ ) { Text(stringResource(R.string.apply)) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun LockTaskFeatures(){ +private fun LockTaskFeatures(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ val lockTaskPolicyList = mutableListOf( LOCK_TASK_FEATURE_NONE, LOCK_TASK_FEATURE_SYSTEM_INFO, @@ -433,7 +490,9 @@ fun LockTaskFeatures(){ custom = false } } - Text(text = stringResource(R.string.lock_task_feature), style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.lock_task_feature), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) if(!inited){ refreshFeature();custom=myDpm.getLockTaskFeatures(myComponent)!=0;inited=true } RadioButtonItem(stringResource(R.string.disable_all),{!custom},{custom=false}) RadioButtonItem(stringResource(R.string.custom),{custom},{custom=true}) @@ -468,28 +527,19 @@ fun LockTaskFeatures(){ ) { Text(stringResource(R.string.apply)) } - Spacer(Modifier.padding(vertical = 4.dp)) + Spacer(Modifier.padding(vertical = 5.dp)) val whitelist = myDpm.getLockTaskPackages(myComponent).toMutableList() var listText by remember{mutableStateOf("")} var inputPkg by remember{mutableStateOf("")} val refreshWhitelist = { inputPkg="" listText="" - var currentItem = whitelist.size - for(each in whitelist){ - currentItem-=1 - listText += each - if(currentItem>0){listText += "\n"} - } + listText = whitelist.toText() } - refreshWhitelist() + LaunchedEffect(Unit){refreshWhitelist()} Text(text = stringResource(R.string.whitelist_app), style = typography.titleLarge) - if(listText!=""){ - SelectionContainer { - Text(text = listText) - } - }else{ - Text(text = stringResource(R.string.none)) + SelectionContainer(modifier = Modifier.animateContentSize(Animations().animateListSize)){ + Text(text = if(listText==""){ stringResource(R.string.none) }else{listText}) } OutlinedTextField( value = inputPkg, @@ -529,24 +579,28 @@ fun LockTaskFeatures(){ Text(stringResource(R.string.remove)) } } + Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -fun CaCert(){ +private fun CaCert(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) var exist by remember{mutableStateOf(false)} - var isEmpty by remember{mutableStateOf(false)} + var isEmpty by remember{mutableStateOf(true)} val refresh = { isEmpty = caCert.isEmpty() exist = if(!isEmpty){ myDpm.hasCaCertInstalled(myComponent, caCert) }else{ false } } - LaunchedEffect(exist){ while(true){ delay(600); refresh() } } - Column{ - Text(text = stringResource(R.string.ca_cert), style = typography.titleLarge) - if(isEmpty){ Text(text = stringResource(R.string.please_select_ca_cert)) }else{ Text(text = stringResource(R.string.cacert_installed, exist)) } + LaunchedEffect(exist){ caCert=byteArrayOf(); while(true){ refresh();delay(600) } } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.ca_cert), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text = if(isEmpty){stringResource(R.string.please_select_ca_cert)}else{stringResource(R.string.cacert_installed, exist)}, modifier = Modifier.animateContentSize()) + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { val caCertIntent = Intent(Intent.ACTION_GET_CONTENT) @@ -598,21 +652,16 @@ fun CaCert(){ @SuppressLint("NewApi") @Composable -fun SecurityLogs(){ +private fun SecurityLogs(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column{ - Text(text = stringResource(R.string.retrieve_security_logs), style = typography.titleLarge) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.retrieve_security_logs), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) Text(text = stringResource(R.string.developing)) - Row(modifier=Modifier.fillMaxWidth().padding(horizontal=8.dp),horizontalArrangement=Arrangement.SpaceBetween,verticalAlignment=Alignment.CenterVertically){ - var checked by remember{mutableStateOf(myDpm.isSecurityLoggingEnabled(myComponent))} - Text(text = stringResource(R.string.enabled), style = typography.titleLarge) - Switch( - checked = checked, - onCheckedChange = {myDpm.setSecurityLoggingEnabled(myComponent,!checked);checked=myDpm.isSecurityLoggingEnabled(myComponent)} - ) - } + SwitchItem(R.string.enable,"",null,{myDpm.isSecurityLoggingEnabled(myComponent)},{myDpm.setSecurityLoggingEnabled(myComponent,it)}) Button( onClick = { val log = myDpm.retrieveSecurityLogs(myComponent) @@ -647,13 +696,13 @@ fun SecurityLogs(){ } @Composable -fun WipeData(){ +private fun WipeData(){ val myContext = LocalContext.current val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ var flag by remember{ mutableIntStateOf(0) } var confirmed by remember{ mutableStateOf(false) } var externalStorage by remember{mutableStateOf(false)} @@ -661,13 +710,15 @@ fun WipeData(){ var euicc by remember{mutableStateOf(false)} var silent by remember{mutableStateOf(false)} var reason by remember{mutableStateOf("")} - Text(text = stringResource(R.string.wipe_data),style = typography.titleLarge,modifier = Modifier.padding(6.dp),color = colorScheme.onErrorContainer) - CheckBoxItem(stringResource(R.string.wipe_external_storage),{externalStorage},{externalStorage=!externalStorage;confirmed=false}, colorScheme.onErrorContainer) + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.wipe_data),style = typography.headlineLarge,modifier = Modifier.padding(6.dp),color = colorScheme.error) + Spacer(Modifier.padding(vertical = 5.dp)) + CheckBoxItem(stringResource(R.string.wipe_external_storage),{externalStorage},{externalStorage=!externalStorage;confirmed=false}) if(VERSION.SDK_INT>=22&&isDeviceOwner(myDpm)){ - CheckBoxItem(stringResource(R.string.wipe_reset_protection_data),{protectionData},{protectionData=!protectionData;confirmed=false}, colorScheme.onErrorContainer) + CheckBoxItem(stringResource(R.string.wipe_reset_protection_data),{protectionData},{protectionData=!protectionData;confirmed=false}) } - if(VERSION.SDK_INT>=28){ CheckBoxItem(stringResource(R.string.wipe_euicc),{euicc},{euicc=!euicc;confirmed=false}, colorScheme.onErrorContainer) } - if(VERSION.SDK_INT>=29){ CheckBoxItem(stringResource(R.string.wipe_silently),{silent},{silent=!silent;confirmed=false}, colorScheme.onErrorContainer) } + if(VERSION.SDK_INT>=28){ CheckBoxItem(stringResource(R.string.wipe_euicc),{euicc},{euicc=!euicc;confirmed=false}) } + if(VERSION.SDK_INT>=29){ CheckBoxItem(stringResource(R.string.wipe_silently),{silent},{silent=!silent;confirmed=false}) } AnimatedVisibility(!silent&&VERSION.SDK_INT>=28) { OutlinedTextField( value = reason, onValueChange = {reason=it}, @@ -678,6 +729,7 @@ fun WipeData(){ modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp) ) } + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { focusMgr.clearFocus() @@ -698,40 +750,40 @@ fun WipeData(){ ) { Text(text = stringResource(if(confirmed){ R.string.cancel }else{ R.string.confirm })) } - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) { + Button( + onClick = { + if(VERSION.SDK_INT>=28){myDpm.wipeData(flag,reason)} + else{myDpm.wipeData(flag)} + }, + colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), + enabled = confirmed&&(VERSION.SDK_INT<34||(VERSION.SDK_INT>=34&&!userManager.isSystemUser)), + modifier = Modifier.fillMaxWidth() + ) { + Text("WipeData") + } + if (VERSION.SDK_INT >= 34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))) { Button( - onClick = { - if(VERSION.SDK_INT>=28){myDpm.wipeData(flag,reason)} - else{myDpm.wipeData(flag)} - }, + onClick = {myDpm.wipeDevice(flag)}, colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), - enabled = confirmed&&(VERSION.SDK_INT<34||(VERSION.SDK_INT>=34&&!userManager.isSystemUser)), - modifier = Modifier.fillMaxWidth(if(VERSION.SDK_INT >= 34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){0.49F}else{1F}) + enabled = confirmed, + modifier = Modifier.fillMaxWidth() ) { - Text("WipeData") - } - if (VERSION.SDK_INT >= 34&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))) { - Button( - onClick = {myDpm.wipeDevice(flag)}, - colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError), - enabled = confirmed, - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text("WipeDevice") - } + Text("WipeDevice") } } + Spacer(Modifier.padding(vertical = 5.dp)) if(VERSION.SDK_INT>=24&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){ - Text(text = stringResource(R.string.will_delete_work_profile)) + Information{Text(text = stringResource(R.string.will_delete_work_profile))} } if(VERSION.SDK_INT>=34&&Binder.getCallingUid()/100000==0){ - Text(text = stringResource(R.string.api34_or_above_wipedata_cannot_in_system_user)) + Information{Text(text = stringResource(R.string.api34_or_above_wipedata_cannot_in_system_user))} } + Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -fun SysUpdatePolicy(){ +private fun SysUpdatePolicy(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) @@ -739,13 +791,66 @@ fun SysUpdatePolicy(){ val sharedPref = myContext.getSharedPreferences("data", Context.MODE_PRIVATE) val isWear = sharedPref.getBoolean("isWear",false) val bodyTextStyle = if(isWear){ typography.bodyMedium}else{typography.bodyLarge} - Column { - if(VERSION.SDK_INT>=26&&isDeviceOwner(myDpm)){ + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + if(VERSION.SDK_INT>=23){ + Column { + var selectedPolicy by remember{ mutableStateOf(myDpm.systemUpdatePolicy?.policyType) } + Text(text = stringResource(R.string.system_update_policy), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + RadioButtonItem(stringResource(R.string.system_update_policy_automatic),{selectedPolicy==TYPE_INSTALL_AUTOMATIC},{selectedPolicy= TYPE_INSTALL_AUTOMATIC}) + RadioButtonItem(stringResource(R.string.system_update_policy_install_windowed),{selectedPolicy==TYPE_INSTALL_WINDOWED},{selectedPolicy= TYPE_INSTALL_WINDOWED}) + RadioButtonItem(stringResource(R.string.system_update_policy_postpone),{selectedPolicy==TYPE_POSTPONE},{selectedPolicy= TYPE_POSTPONE}) + RadioButtonItem(stringResource(R.string.none),{selectedPolicy == null},{selectedPolicy=null}) + var windowedPolicyStart by remember{ mutableStateOf("") } + var windowedPolicyEnd by remember{ mutableStateOf("") } + if(selectedPolicy==2){ + Spacer(Modifier.padding(vertical = 3.dp)) + OutlinedTextField( + value = windowedPolicyStart, + label = { Text(stringResource(R.string.start_time))}, + onValueChange = {windowedPolicyStart=it}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth(0.5F) + ) + Spacer(Modifier.padding(horizontal = 3.dp)) + OutlinedTextField( + value = windowedPolicyEnd, + onValueChange = {windowedPolicyEnd=it}, + label = {Text(stringResource(R.string.end_time))}, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth() + ) + Spacer(Modifier.padding(vertical = 3.dp)) + Text(text = stringResource(R.string.minutes_in_one_day), style = bodyTextStyle) + } + Button( + onClick = { + val policy = + when(selectedPolicy){ + TYPE_INSTALL_AUTOMATIC-> SystemUpdatePolicy.createAutomaticInstallPolicy() + TYPE_INSTALL_WINDOWED-> SystemUpdatePolicy.createWindowedInstallPolicy(windowedPolicyStart.toInt(),windowedPolicyEnd.toInt()) + TYPE_POSTPONE-> SystemUpdatePolicy.createPostponeInstallPolicy() + else->null + } + myDpm.setSystemUpdatePolicy(myComponent,policy) + Toast.makeText(myContext, "成功!", Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + } + } + if(VERSION.SDK_INT>=26){ + Spacer(Modifier.padding(vertical = 10.dp)) val sysUpdateInfo = myDpm.getPendingSystemUpdate(myComponent) - Column(modifier = sections()) { + Column { if(sysUpdateInfo!=null){ - Text(text = "Update first available: ${Date(sysUpdateInfo.receivedTime)}", style = bodyTextStyle) - Text(text = "Hash code: ${sysUpdateInfo.hashCode()}", style = bodyTextStyle) + Text(text = stringResource(R.string.update_received_time, Date(sysUpdateInfo.receivedTime)), style = bodyTextStyle) val securityStateDesc = when(sysUpdateInfo.securityPatchState){ SystemUpdateInfo.SECURITY_PATCH_STATE_UNKNOWN-> stringResource(R.string.unknown) SystemUpdateInfo.SECURITY_PATCH_STATE_TRUE->"true" @@ -757,55 +862,7 @@ fun SysUpdatePolicy(){ } } } - if(VERSION.SDK_INT>=23){ - Column(modifier = sections()) { - var selectedPolicy by remember{ mutableStateOf(myDpm.systemUpdatePolicy?.policyType) } - Text(text = stringResource(R.string.system_update_policy), style = typography.titleLarge, color = colorScheme.onPrimaryContainer) - RadioButtonItem(stringResource(R.string.system_update_policy_automatic),{selectedPolicy==SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC},{selectedPolicy= SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC}) - RadioButtonItem(stringResource(R.string.system_update_policy_install_windowed),{selectedPolicy==SystemUpdatePolicy.TYPE_INSTALL_WINDOWED},{selectedPolicy= SystemUpdatePolicy.TYPE_INSTALL_WINDOWED}) - RadioButtonItem(stringResource(R.string.system_update_policy_postpone),{selectedPolicy==SystemUpdatePolicy.TYPE_POSTPONE},{selectedPolicy= SystemUpdatePolicy.TYPE_POSTPONE}) - RadioButtonItem(stringResource(R.string.none),{selectedPolicy == null},{selectedPolicy=null}) - var windowedPolicyStart by remember{ mutableStateOf("") } - var windowedPolicyEnd by remember{ mutableStateOf("") } - if(selectedPolicy==2){ - Spacer(Modifier.padding(vertical = 3.dp)) - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.Center) { - OutlinedTextField( - value = windowedPolicyStart, - label = { Text(stringResource(R.string.start_time))}, - onValueChange = {windowedPolicyStart=it}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth(0.5F) - ) - Spacer(Modifier.padding(horizontal = 3.dp)) - OutlinedTextField( - value = windowedPolicyEnd, - onValueChange = {windowedPolicyEnd=it}, - label = {Text(stringResource(R.string.end_time))}, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth() - ) - } - Spacer(Modifier.padding(vertical = 3.dp)) - Text(text = stringResource(R.string.minutes_in_one_day), style = bodyTextStyle) - } - val policy = - when(selectedPolicy){ - SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC-> SystemUpdatePolicy.createAutomaticInstallPolicy() - SystemUpdatePolicy.TYPE_INSTALL_WINDOWED-> SystemUpdatePolicy.createWindowedInstallPolicy(windowedPolicyStart.toInt(),windowedPolicyEnd.toInt()) - SystemUpdatePolicy.TYPE_POSTPONE-> SystemUpdatePolicy.createPostponeInstallPolicy() - else->null - } - Button( - onClick = {myDpm.setSystemUpdatePolicy(myComponent,policy);Toast.makeText(myContext, "成功!", Toast.LENGTH_SHORT).show()}, - enabled = isDeviceOwner(myDpm), - modifier = Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.apply)) - } - }} + Spacer(Modifier.padding(vertical = 30.dp)) /*if(VERSION.SDK_INT>=29){ Column(modifier = sections()){ var resultUri by remember{mutableStateOf(otaUri)} diff --git a/app/src/main/java/com/binbin/androidowner/dpm/UserManage.kt b/app/src/main/java/com/binbin/androidowner/dpm/UserManage.kt index 8c31110..2617987 100644 --- a/app/src/main/java/com/binbin/androidowner/dpm/UserManage.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/UserManage.kt @@ -8,150 +8,156 @@ import android.content.Intent import android.graphics.BitmapFactory import android.os.Binder import android.os.Build.VERSION +import android.os.Process import android.os.UserHandle import android.os.UserManager import android.provider.MediaStore import android.widget.Toast import androidx.activity.ComponentActivity -import androidx.compose.foundation.focusable +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState 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.Button +import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.core.os.UserManagerCompat -import com.binbin.androidowner.ui.CheckBoxItem -import com.binbin.androidowner.ui.RadioButtonItem +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.binbin.androidowner.R +import com.binbin.androidowner.toText +import com.binbin.androidowner.ui.* import com.binbin.androidowner.uriToStream +import kotlinx.coroutines.delay var affiliationID = mutableSetOf() +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun UserManage() { - Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager - val titleColor = colorScheme.onPrimaryContainer - Column{ - Text(text = "用户信息", style = typography.titleLarge, color = titleColor) - Text("用户已解锁:${UserManagerCompat.isUserUnlocked(myContext)}") - if(VERSION.SDK_INT>=24){ Text("支持多用户:${UserManager.supportsMultipleUsers()}") } - if(VERSION.SDK_INT>=23){ Text(text = "系统用户:${userManager.isSystemUser}") } - if(VERSION.SDK_INT>=34){ Text(text = "管理员用户:${userManager.isAdminUser}") } - if(VERSION.SDK_INT>=31){ Text(text = "无头系统用户: ${UserManager.isHeadlessSystemUserMode()}") } - Spacer(Modifier.padding(vertical = 5.dp)) - if (VERSION.SDK_INT >= 28) { - val logoutable = myDpm.isLogoutEnabled - Text(text = "用户可以退出 : $logoutable") - if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ - val ephemeralUser = myDpm.isEphemeralUser(myComponent) - Text(text = "临时用户: $ephemeralUser") - } - Text(text = "附属用户: ${myDpm.isAffiliatedUser}") - } - Spacer(Modifier.padding(vertical = 5.dp)) - Text(text = "当前UserID:${Binder.getCallingUid()/100000}") - Text(text = "当前用户序列号:${userManager.getSerialNumberForUser(android.os.Process.myUserHandle())}") +fun UserManage(navCtrl:NavHostController) { + val localNavCtrl = rememberNavController() + val backStackEntry by localNavCtrl.currentBackStackEntryAsState() + val titleMap = mapOf( + "UserInfo" to R.string.user_info, + "UserOperation" to R.string.user_operation, + "CreateUser" to R.string.create_user, + "EditUsername" to R.string.edit_username, + "ChangeUserIcon" to R.string.change_user_icon, + "UserSessionMessage" to R.string.user_session_msg, + "AffiliationID" to R.string.affiliation_id, + ) + Scaffold( + topBar = { + TopAppBar( + title = {Text(text = stringResource(titleMap[backStackEntry?.destination?.route]?:R.string.user_manage))}, + navigationIcon = {NavIcon{if(backStackEntry?.destination?.route=="Home"){navCtrl.navigateUp()}else{localNavCtrl.navigateUp()}}}, + colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surfaceVariant) + ) } + ){ + NavHost( + navController = localNavCtrl, startDestination = "Home", + enterTransition = Animations().navHostEnterTransition, + exitTransition = Animations().navHostExitTransition, + popEnterTransition = Animations().navHostPopEnterTransition, + popExitTransition = Animations().navHostPopExitTransition, + modifier = Modifier + .background(color = if(isSystemInDarkTheme()) { colorScheme.background }else{ colorScheme.primary.copy(alpha = 0.05F) }) + .padding(top = it.calculateTopPadding()) + ){ + composable(route = "Home"){Home(localNavCtrl)} + composable(route = "UserInfo"){CurrentUserInfo()} + composable(route = "UserOperation"){UserOperation()} + composable(route = "CreateUser"){CreateUser()} + composable(route = "EditUsername"){Username()} + composable(route = "ChangeUserIcon"){UserIcon()} + composable(route = "UserSessionMessage"){UserSessionMessage()} + composable(route = "AffiliationID"){AffiliationID()} + } + } +} - UserOperation() - +@Composable +private fun Home(navCtrl: NavHostController){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + SubPageItem(R.string.user_info,""){navCtrl.navigate("UserInfo")} + SubPageItem(R.string.user_operation,""){navCtrl.navigate("UserOperation")} if(VERSION.SDK_INT>=24&&isDeviceOwner(myDpm)){ - CreateUser() + SubPageItem(R.string.create_user,""){navCtrl.navigate("CreateUser")} } - - if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - AffiliationID() - } - - UserSessionMessage("用户名", "用户名", true, {null}) {msg-> myDpm.setProfileName(myComponent, msg.toString())} - + SubPageItem(R.string.edit_username,""){navCtrl.navigate("EditUsername")} if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ - UserIcon() + SubPageItem(R.string.change_user_icon,""){navCtrl.navigate("ChangeUserIcon")} } - - if(VERSION.SDK_INT>=28){ - UserSessionMessage("用户会话开始消息", "消息", false, {myDpm.getStartUserSessionMessage(myComponent)}) {msg-> myDpm.setStartUserSessionMessage(myComponent, msg)} - UserSessionMessage("用户会话结束消息", "消息", false, {myDpm.getEndUserSessionMessage(myComponent)}) {msg-> myDpm.setEndUserSessionMessage(myComponent, msg)} + if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){ + SubPageItem(R.string.user_session_msg,""){navCtrl.navigate("UserSessionMessage")} + } + if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){ + SubPageItem(R.string.affiliation_id,""){navCtrl.navigate("AffiliationID")} } Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -private fun UserSessionMessage(text:String, textField:String, profileOwner:Boolean, get: ()->CharSequence?, setMsg:(msg: CharSequence?)->Unit){ - Column{ - val myContext = LocalContext.current - val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val focusMgr = LocalFocusManager.current - var msg by remember{ mutableStateOf(if(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner)){ if(get()==null){""}else{get().toString()} }else{""}) } - val sharedPref = myContext.getSharedPreferences("data", Context.MODE_PRIVATE) - val isWear = sharedPref.getBoolean("isWear",false) - Text(text = text, style = typography.titleLarge, color = colorScheme.onPrimaryContainer) - TextField( - value = msg, - onValueChange = {msg=it}, - label = {Text(textField)}, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 6.dp), - enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner) - ) - Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) { - Button( - onClick = { - focusMgr.clearFocus() - setMsg(msg) - msg = if(get()==null){""}else{get().toString()} - Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner), - modifier = Modifier.fillMaxWidth(if(isWear){0.49F}else{0.65F}) - ) { - Text("应用") - } - Button( - onClick = { - focusMgr.clearFocus() - setMsg(null) - msg = get()?.toString() ?: "" - Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() - }, - enabled = isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&profileOwner), - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text("默认") +private fun CurrentUserInfo(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.user_info), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Text(stringResource(R.string.is_user_unlocked, UserManagerCompat.isUserUnlocked(myContext))) + if(VERSION.SDK_INT>=24){ Text(stringResource(R.string.is_support_multi_user, UserManager.supportsMultipleUsers())) } + if(VERSION.SDK_INT>=23){ Text(text = stringResource(R.string.is_system_user, userManager.isSystemUser)) } + if(VERSION.SDK_INT>=34){ Text(text = stringResource(R.string.is_admin_user, userManager.isAdminUser)) } + if(VERSION.SDK_INT>=31){ Text(text = stringResource(R.string.is_headless_system_user, UserManager.isHeadlessSystemUserMode())) } + Spacer(Modifier.padding(vertical = 5.dp)) + if (VERSION.SDK_INT >= 28) { + val logoutable = myDpm.isLogoutEnabled + Text(text = stringResource(R.string.user_can_logout, logoutable)) + if(isDeviceOwner(myDpm)|| isProfileOwner(myDpm)){ + val ephemeralUser = myDpm.isEphemeralUser(myComponent) + Text(text = stringResource(R.string.is_ephemeral_user, ephemeralUser)) } + Text(text = stringResource(R.string.is_affiliated_user, myDpm.isAffiliatedUser)) } + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text = stringResource(R.string.user_id_is, Binder.getCallingUid()/100000)) + Text(text = stringResource(R.string.user_serial_number_is, userManager.getSerialNumberForUser(Process.myUserHandle()))) } } @Composable -fun UserOperation(){ +private fun UserOperation(){ val myContext = LocalContext.current val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ - Text(text = "用户操作", style = typography.titleLarge) + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.user_operation), style = typography.headlineLarge) var idInput by remember{ mutableStateOf("") } - var userHandleById:UserHandle by remember{ mutableStateOf(android.os.Process.myUserHandle()) } + var userHandleById:UserHandle by remember{ mutableStateOf(Process.myUserHandle()) } var useUid by remember{ mutableStateOf(false) } + Spacer(Modifier.padding(vertical = 5.dp)) TextField( value = idInput, onValueChange = { @@ -162,128 +168,127 @@ fun UserOperation(){ } }else{ val userHandleBySerial = userManager.getUserForSerialNumber(idInput.toLong()) - userHandleById = userHandleBySerial ?: android.os.Process.myUserHandle() + userHandleById = userHandleBySerial ?: Process.myUserHandle() } }, - label = {Text(if(useUid){"UID"}else{"序列号"})}, + label = {Text(if(useUid){"UID"}else{ stringResource(R.string.serial_number) })}, enabled = isDeviceOwner(myDpm), - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp), + modifier = Modifier.focusable().fillMaxWidth(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) ) - if(VERSION.SDK_INT>=24&&isDeviceOwner(myDpm)){ - CheckBoxItem(text = "使用UID", checked = {useUid}, operation = {idInput=""; useUid = !useUid}) + Spacer(Modifier.padding(vertical = 3.dp)) + if(VERSION.SDK_INT>=24){ + CheckBoxItem(text = stringResource(R.string.use_uid), checked = {useUid}, operation = {idInput=""; useUid = !useUid}) } + Spacer(Modifier.padding(vertical = 5.dp)) if(VERSION.SDK_INT>28){ if(isProfileOwner(myDpm)&&myDpm.isAffiliatedUser){ Button( onClick = { val result = myDpm.logoutUser(myComponent) - Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show() + Toast.makeText(myContext, userOperationResultCode(result,myContext), Toast.LENGTH_SHORT).show() }, enabled = isProfileOwner(myDpm), modifier = Modifier.fillMaxWidth() ) { - Text("登出当前用户") + Text(stringResource(R.string.logout_current_user)) } } } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + if(VERSION.SDK_INT>=28){ Button( onClick = { focusMgr.clearFocus() - if(VERSION.SDK_INT>=28){ - val result = myDpm.startUserInBackground(myComponent,userHandleById) - Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show() - } - }, - enabled = isDeviceOwner(myDpm)&&VERSION.SDK_INT>=28, - modifier = Modifier.fillMaxWidth(0.49F) - ){ - Text("在后台启动") - } - Button( - onClick = { - focusMgr.clearFocus() - if(myDpm.switchUser(myComponent,userHandleById)){ - Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() - }else{ - Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() - } + val result = myDpm.startUserInBackground(myComponent,userHandleById) + Toast.makeText(myContext, userOperationResultCode(result,myContext), Toast.LENGTH_SHORT).show() }, enabled = isDeviceOwner(myDpm), - modifier = Modifier.fillMaxWidth(0.96F) - ) { - Text("切换") + modifier = Modifier.fillMaxWidth() + ){ + Text(stringResource(R.string.start_in_background)) } } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + focusMgr.clearFocus() + Toast.makeText( + myContext, + myContext.getString(if(myDpm.switchUser(myComponent,userHandleById)) { R.string.success }else{ R.string.fail }), Toast.LENGTH_SHORT + ).show() + }, + enabled = isDeviceOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.user_operation_switch)) + } + if(VERSION.SDK_INT>=28){ Button( onClick = { focusMgr.clearFocus() try{ - if(VERSION.SDK_INT>=28){ - val result = myDpm.stopUser(myComponent,userHandleById) - Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show() - } + val result = myDpm.stopUser(myComponent,userHandleById) + Toast.makeText(myContext, userOperationResultCode(result,myContext), Toast.LENGTH_SHORT).show() }catch(e:IllegalArgumentException){ - Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() - } - }, - enabled = isDeviceOwner(myDpm)&&VERSION.SDK_INT>=28, - modifier = Modifier.fillMaxWidth(0.49F) - ) { - Text("停止") - } - Button( - onClick = { - focusMgr.clearFocus() - if(myDpm.removeUser(myComponent,userHandleById)){ - Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() - idInput="" - }else{ - Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() + Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() } }, enabled = isDeviceOwner(myDpm), - modifier = Modifier.fillMaxWidth(0.96F) + modifier = Modifier.fillMaxWidth() ) { - Text("移除") + Text(stringResource(R.string.user_operation_stop)) } } - if(VERSION.SDK_INT<28){ - Text(text = "停止用户需API28") + Button( + onClick = { + focusMgr.clearFocus() + if(myDpm.removeUser(myComponent,userHandleById)){ + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + idInput="" + }else{ + Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() + } + }, + enabled = isDeviceOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.user_operation_remove)) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun CreateUser(){ +private fun CreateUser(){ val myContext = LocalContext.current val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ - var userName by remember{ mutableStateOf("") } - Text(text = "创建用户", style = typography.titleLarge) + var userName by remember{ mutableStateOf("") } + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.create_user), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) TextField( value = userName, onValueChange = {userName=it}, - label = {Text("用户名")}, - modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp), + label = {Text(stringResource(R.string.username))}, + modifier = Modifier.focusable().fillMaxWidth(), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) ) + Spacer(Modifier.padding(vertical = 5.dp)) var selectedFlag by remember{ mutableIntStateOf(0) } - RadioButtonItem("无",{selectedFlag==0},{selectedFlag=0}) - RadioButtonItem("跳过创建用户向导",{selectedFlag==DevicePolicyManager.SKIP_SETUP_WIZARD},{selectedFlag=DevicePolicyManager.SKIP_SETUP_WIZARD}) + RadioButtonItem(stringResource(R.string.none),{selectedFlag==0},{selectedFlag=0}) + RadioButtonItem(stringResource(R.string.create_user_skip_wizard),{selectedFlag==DevicePolicyManager.SKIP_SETUP_WIZARD},{selectedFlag=DevicePolicyManager.SKIP_SETUP_WIZARD}) if(VERSION.SDK_INT>=28){ - RadioButtonItem("临时用户",{selectedFlag==DevicePolicyManager.MAKE_USER_EPHEMERAL},{selectedFlag=DevicePolicyManager.MAKE_USER_EPHEMERAL}) - RadioButtonItem("启用所有系统应用",{selectedFlag==DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED},{selectedFlag=DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED}) + RadioButtonItem(stringResource(R.string.create_user_ephemeral_user),{selectedFlag==DevicePolicyManager.MAKE_USER_EPHEMERAL},{selectedFlag=DevicePolicyManager.MAKE_USER_EPHEMERAL}) + RadioButtonItem(stringResource(R.string.create_user_enable_all_system_app),{selectedFlag==DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED},{selectedFlag=DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED}) } var newUserHandle: UserHandle? by remember{ mutableStateOf(null) } + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { newUserHandle=myDpm.createAndManageUser(myComponent,userName,myComponent,null,selectedFlag) @@ -293,30 +298,39 @@ fun CreateUser(){ enabled = isDeviceOwner(myDpm), modifier = Modifier.fillMaxWidth() ) { - Text("创建(Owner)") + Text(stringResource(R.string.create)) } - if(newUserHandle!=null){ Text(text = "新用户的序列号:${userManager.getSerialNumberForUser(newUserHandle)}") } + Spacer(Modifier.padding(vertical = 5.dp)) + if(newUserHandle!=null){ Text(text = stringResource(R.string.serial_number_of_new_user_is, userManager.getSerialNumberForUser(newUserHandle))) } + Spacer(Modifier.padding(vertical = 30.dp)) } } @SuppressLint("NewApi") @Composable -fun AffiliationID(){ +private fun AffiliationID(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - Column{ - var input by remember{mutableStateOf("")} - var list by remember{mutableStateOf("")} - val refresh = { - list = "" - var count = affiliationID.size - for(item in affiliationID){ count-=1; list+=item; if(count>0){list+="\n"} } + var input by remember{mutableStateOf("")} + var list by remember{mutableStateOf("")} + LaunchedEffect(Unit){ + affiliationID = myDpm.getAffiliationIds(myComponent) + list = affiliationID.toText() + } + 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)) } - var inited by remember{mutableStateOf(false)} - if(!inited){affiliationID = myDpm.getAffiliationIds(myComponent);refresh();inited=true} - Text(text = "附属用户ID", style = typography.titleLarge) + Spacer(Modifier.padding(vertical = 5.dp)) TextField( value = input, onValueChange = {input = it}, @@ -325,59 +339,162 @@ fun AffiliationID(){ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}) ) - if(list!=""){ - SelectionContainer { - Text(text = list) - } - }else{ - Text(text = "无") - } + Spacer(Modifier.padding(vertical = 5.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ Button( - onClick = { affiliationID.add(input); refresh() }, + onClick = { affiliationID.add(input); list = affiliationID.toText() }, modifier = Modifier.fillMaxWidth(0.49F) ){ - Text("添加") + Text(stringResource(R.string.add)) } Button( - onClick = { affiliationID.remove(input); refresh() }, + onClick = { affiliationID.remove(input); list = affiliationID.toText() }, modifier = Modifier.fillMaxWidth(0.96F) ){ - Text("移除") + Text(stringResource(R.string.remove)) } } Button( onClick = { if("" in affiliationID) { - Toast.makeText(myContext, "有空字符串", Toast.LENGTH_SHORT).show() + Toast.makeText(myContext, myContext.getString(R.string.include_empty_string), Toast.LENGTH_SHORT).show() }else if(affiliationID.isEmpty()){ - Toast.makeText(myContext, "不能为空", Toast.LENGTH_SHORT).show() + Toast.makeText(myContext, myContext.getString(R.string.cannot_be_empty), Toast.LENGTH_SHORT).show() }else{ myDpm.setAffiliationIds(myComponent, affiliationID) affiliationID = myDpm.getAffiliationIds(myComponent) - refresh() - Toast.makeText(myContext,"成功",Toast.LENGTH_SHORT).show() + list = affiliationID.toText() + Toast.makeText(myContext,myContext.getString(R.string.success),Toast.LENGTH_SHORT).show() } }, modifier = Modifier.fillMaxWidth() ) { - Text("应用") + Text(stringResource(R.string.apply)) + } + Spacer(Modifier.padding(vertical = 5.dp)) + Information{Text(text = stringResource(R.string.affiliation_id_desc))} + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +private fun Username(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + val focusMgr = LocalFocusManager.current + var inputUsername by remember{mutableStateOf("")} + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.edit_username), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + TextField( + value = inputUsername, + onValueChange = {inputUsername=it}, + label = {Text(stringResource(R.string.username))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth(), + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) + ) + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = { + myDpm.setProfileName(myComponent,inputUsername) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + Button( + onClick = { + myDpm.setProfileName(myComponent,null) + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.reset)) } - Text(text = "如果多用户,附属用户ID相同时可以让其他用户附属于主用户") } } @SuppressLint("NewApi") @Composable -fun UserIcon(){ +private fun UserSessionMessage(){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - Column{ - var getContent by remember{mutableStateOf(false)} - Text(text = "用户图标", style = typography.titleLarge) - Text(text = "尽量选择正方形的图片,以免产生问题") - CheckBoxItem("使用文件选择器而不是相册",{getContent},{getContent=!getContent}) + val focusMgr = LocalFocusManager.current + var start by remember{mutableStateOf(myDpm.getStartUserSessionMessage(myComponent).toString())} + var end by remember{mutableStateOf(myDpm.getEndUserSessionMessage(myComponent).toString())} + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.user_session_msg), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + TextField( + value = start, + onValueChange = {start=it}, + label = {Text(stringResource(R.string.start_user_session_msg))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth(), + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) + ) + Spacer(Modifier.padding(vertical = 5.dp)) + TextField( + value = end, + onValueChange = {end=it}, + label = {Text(stringResource(R.string.end_user_session_msg))}, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}), + modifier = Modifier.focusable().fillMaxWidth(), + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm) + ) + Spacer(Modifier.padding(vertical = 5.dp)) + Button( + onClick = { + myDpm.setStartUserSessionMessage(myComponent,start) + myDpm.setEndUserSessionMessage(myComponent,end) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } + Button( + onClick = { + myDpm.setStartUserSessionMessage(myComponent,null) + myDpm.setEndUserSessionMessage(myComponent,null) + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.reset)) + } + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@SuppressLint("NewApi") +@Composable +private fun UserIcon(){ + val myContext = LocalContext.current + val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) + var getContent by remember{mutableStateOf(false)} + var canApply by remember{mutableStateOf(false)} + Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){ + Spacer(Modifier.padding(vertical = 10.dp)) + Text(text = stringResource(R.string.change_user_icon), style = typography.headlineLarge) + Spacer(Modifier.padding(vertical = 5.dp)) + Text(text = stringResource(R.string.pick_a_rect_picture)) + Spacer(Modifier.padding(vertical = 5.dp)) + CheckBoxItem(stringResource(R.string.file_picker_instead_gallery),{getContent},{getContent=!getContent}) + Spacer(Modifier.padding(vertical = 5.dp)) Button( onClick = { val intent = Intent(if(getContent){Intent.ACTION_GET_CONTENT}else{Intent.ACTION_PICK}) @@ -387,33 +504,32 @@ fun UserIcon(){ }, modifier = Modifier.fillMaxWidth() ) { - Text("选择图片...") + Text(stringResource(R.string.select_picture)) } - Button( - onClick = { - if(userIconUri!=null){ + LaunchedEffect(Unit){ delay(600); canApply = userIconUri!=null } + AnimatedVisibility(canApply) { + Button( + onClick = { uriToStream(myContext, userIconUri){stream -> val bitmap = BitmapFactory.decodeStream(stream) myDpm.setUserIcon(myComponent,bitmap) - Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() + Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show() } - }else{ - Toast.makeText(myContext, "请先选择图片", Toast.LENGTH_SHORT).show() - } - }, - modifier = Modifier.fillMaxWidth() - ) { - Text("应用") + }, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.apply)) + } } } } -private fun userOperationResultCode(result:Int): String { +private fun userOperationResultCode(result:Int, myContext: Context): String { return when(result){ - UserManager.USER_OPERATION_SUCCESS->"成功" - UserManager.USER_OPERATION_ERROR_UNKNOWN->"未知结果(失败)" - UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE->"失败:受管理的资料" - UserManager.USER_OPERATION_ERROR_CURRENT_USER->"失败:当前用户" + UserManager.USER_OPERATION_SUCCESS->myContext.getString(R.string.success) + UserManager.USER_OPERATION_ERROR_UNKNOWN-> myContext.getString(R.string.unknown_result) + UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE-> myContext.getString(R.string.fail_managed_profile) + UserManager.USER_OPERATION_ERROR_CURRENT_USER-> myContext.getString(R.string.fail_current_user) else->"未知" } } diff --git a/app/src/main/java/com/binbin/androidowner/dpm/UserRestriction.kt b/app/src/main/java/com/binbin/androidowner/dpm/UserRestriction.kt index e6ae491..a993ce6 100644 --- a/app/src/main/java/com/binbin/androidowner/dpm/UserRestriction.kt +++ b/app/src/main/java/com/binbin/androidowner/dpm/UserRestriction.kt @@ -1,5 +1,6 @@ package com.binbin.androidowner.dpm +import android.annotation.SuppressLint import android.app.admin.DevicePolicyManager import android.content.ComponentName import android.content.Context @@ -9,28 +10,33 @@ import android.widget.Toast import androidx.activity.ComponentActivity import androidx.annotation.DrawableRes import androidx.annotation.StringRes -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon +import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController import com.binbin.androidowner.R +import com.binbin.androidowner.ui.Animations +import com.binbin.androidowner.ui.NavIcon +import com.binbin.androidowner.ui.SubPageItem +import com.binbin.androidowner.ui.SwitchItem private data class Restriction( val restriction:String, @@ -39,99 +45,135 @@ private data class Restriction( @DrawableRes val ico:Int ) +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun UserRestriction(){ +fun UserRestriction(navCtrl: NavHostController){ + val localNavCtrl = rememberNavController() + val backStackEntry by localNavCtrl.currentBackStackEntryAsState() + val titleMap = mapOf( + "Internet" to R.string.network_internet, + "Connectivity" to R.string.more_connectivity, + "Users" to R.string.users, + "Media" to R.string.media, + "Applications" to R.string.applications, + "Other" to R.string.other + ) + Scaffold( + topBar = { + TopAppBar( + title = {Text(text = stringResource(titleMap[backStackEntry?.destination?.route]?:R.string.user_restrict))}, + navigationIcon = {NavIcon{if(backStackEntry?.destination?.route=="Home"){navCtrl.navigateUp()}else{localNavCtrl.navigateUp()}}}, + colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surfaceVariant) + ) + } + ){ + NavHost( + navController = localNavCtrl, startDestination = "Home", + enterTransition = Animations().navHostEnterTransition, + exitTransition = Animations().navHostExitTransition, + popEnterTransition = Animations().navHostPopEnterTransition, + popExitTransition = Animations().navHostPopExitTransition, + modifier = Modifier + .background(color = if(isSystemInDarkTheme()) { colorScheme.background }else{ colorScheme.primary.copy(alpha = 0.05F) }) + .padding(top = it.calculateTopPadding()) + ){ + composable(route = "Internet"){Internet()} + composable(route = "Home"){Home(localNavCtrl)} + composable(route = "Connectivity"){Connectivity()} + composable(route = "Applications"){Application()} + composable(route = "Users"){User()} + composable(route = "Media"){Media()} + composable(route = "Other"){Other()} + } + } +} + +@Composable +private fun Home(navCtrl:NavHostController){ val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - var internetVisible by remember{ mutableStateOf(false) } - var connectivityVisible by remember{ mutableStateOf(false) } - var applicationVisible by remember{ mutableStateOf(false) } - var mediaVisible by remember{ mutableStateOf(false) } - var userVisible by remember{ mutableStateOf(false) } - var otherVisible by remember{ mutableStateOf(false) } Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally){ + Spacer(Modifier.padding(vertical = 5.dp)) Text(text = "打开开关后会禁用对应的功能") - if(VERSION.SDK_INT<24){ - Text(text = "所有的用户限制都需要API24,你的设备低于API24,无法使用。", color = colorScheme.error) - } - if(isProfileOwner(myDpm)){ - Text(text = "Profile owner无法使用部分功能") - } - if(isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)))){ - Text(text = "工作资料中部分功能无效") - } - SectionTab("网络和互联网",{internetVisible}, { internetVisible=!internetVisible}) - AnimatedVisibility(internetVisible) { - Column { - for(internetItem in RestrictionData().internet()){ - UserRestrictionItem(internetItem.restriction,internetItem.name,internetItem.desc,internetItem.ico) - } - } - } - SectionTab("更多连接",{connectivityVisible}) { connectivityVisible=!connectivityVisible } - AnimatedVisibility(connectivityVisible) { - Column { - for(connectivityItem in RestrictionData().connectivity(myContext)){ - UserRestrictionItem(connectivityItem.restriction,connectivityItem.name,connectivityItem.desc,connectivityItem.ico) - } - } - } - SectionTab("应用",{applicationVisible}) { applicationVisible=!applicationVisible } - AnimatedVisibility(applicationVisible) { - Column { - for(applicationItem in RestrictionData().application(myContext)){ - UserRestrictionItem(applicationItem.restriction,applicationItem.name,applicationItem.desc,applicationItem.ico) - } - } - } - SectionTab("用户",{userVisible}) { userVisible=!userVisible } - AnimatedVisibility(userVisible) { - Column { - for(userItem in RestrictionData().user()){ - UserRestrictionItem(userItem.restriction,userItem.name,userItem.desc,userItem.ico) - } - } - } - SectionTab("媒体",{mediaVisible}) { mediaVisible=!mediaVisible } - AnimatedVisibility(mediaVisible) { - Column { - for(mediaItem in RestrictionData().media()){ - UserRestrictionItem(mediaItem.restriction,mediaItem.name,mediaItem.desc,mediaItem.ico) - } - } - } - SectionTab("其他",{otherVisible}) { otherVisible=!otherVisible } - AnimatedVisibility(otherVisible) { - Column { - for(otherItem in RestrictionData().other(myContext)){ - UserRestrictionItem(otherItem.restriction,otherItem.name,otherItem.desc,otherItem.ico) - } - } + if(isProfileOwner(myDpm)){ Text(text = "Profile owner无法使用部分功能") } + if(isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)))){ Text(text = "工作资料中部分功能无效") } + Spacer(Modifier.padding(vertical = 2.dp)) + SubPageItem(R.string.network_internet,""){navCtrl.navigate("Internet")} + SubPageItem(R.string.more_connectivity,""){navCtrl.navigate("Connectivity")} + SubPageItem(R.string.applications,""){navCtrl.navigate("Applications")} + SubPageItem(R.string.users,""){navCtrl.navigate("Users")} + SubPageItem(R.string.media,""){navCtrl.navigate("Media")} + SubPageItem(R.string.other,""){navCtrl.navigate("Other")} + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@SuppressLint("NewApi") +@Composable +private fun Internet(){ + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + for(internetItem in RestrictionData().internet()){ + UserRestrictionItem(internetItem.restriction,internetItem.name,internetItem.desc,internetItem.ico) } Spacer(Modifier.padding(vertical = 30.dp)) } } @Composable -fun SectionTab(txt:String,getSection:()->Boolean,setSection:()->Unit){ - val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) - Text( - text = txt, - color = if(getSection()){ colorScheme.onTertiaryContainer.copy(alpha = 0.8F) }else{ colorScheme.onPrimaryContainer.copy(alpha = 0.8F) }, - textAlign = TextAlign.Center, - style = if(!sharedPref.getBoolean("isWear",false)){typography.headlineMedium}else{typography.titleLarge}, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = if(!sharedPref.getBoolean("isWear",false)){8.dp}else{4.dp}, - vertical = if(!sharedPref.getBoolean("isWear",false)){5.dp}else{2.dp}) - .clip(RoundedCornerShape(15.dp)) - .background(color = if (getSection()){ colorScheme.tertiaryContainer }else{ colorScheme.primaryContainer }.copy(0.8F)) - .clickable(onClick = setSection) - .padding(vertical = if(!sharedPref.getBoolean("isWear",false)){8.dp}else{3.dp}) - ) +private fun Connectivity(){ + val myContext = LocalContext.current + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + for(connectivityItem in RestrictionData().connectivity(myContext)){ + UserRestrictionItem(connectivityItem.restriction,connectivityItem.name,connectivityItem.desc,connectivityItem.ico) + } + Spacer(Modifier.padding(vertical = 30.dp)) + } } +@Composable +fun Application(){ + val myContext = LocalContext.current + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + for(applicationItem in RestrictionData().application(myContext)){ + UserRestrictionItem(applicationItem.restriction,applicationItem.name,applicationItem.desc,applicationItem.ico) + } + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +private fun User(){ + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + for(userItem in RestrictionData().user()){ + UserRestrictionItem(userItem.restriction,userItem.name,userItem.desc,userItem.ico) + } + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +private fun Media(){ + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + for(mediaItem in RestrictionData().media()){ + UserRestrictionItem(mediaItem.restriction,mediaItem.name,mediaItem.desc,mediaItem.ico) + } + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@Composable +private fun Other(){ + val myContext = LocalContext.current + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){ + for(otherItem in RestrictionData().other(myContext)){ + UserRestrictionItem(otherItem.restriction,otherItem.name,otherItem.desc,otherItem.ico) + } + Spacer(Modifier.padding(vertical = 30.dp)) + } +} + +@SuppressLint("NewApi") @Composable private fun UserRestrictionItem( restriction:String, itemName:Int, @@ -141,58 +183,24 @@ private fun UserRestrictionItem( val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) - var strictState by remember{ mutableStateOf(false) } - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ){ - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(0.8F) - ) { - Icon( - painter = painterResource(leadIcon), - contentDescription = null, - modifier = Modifier.padding(start = 4.dp, end = 8.dp), - tint = colorScheme.secondary - ) - Column{ - Text( - text = stringResource(itemName), - style = typography.titleLarge, - color = colorScheme.onSecondaryContainer - ) - if(restrictionDescription!=""){ - Text(text = restrictionDescription, color = colorScheme.onSecondaryContainer, style = typography.titleLarge) + SwitchItem( + itemName,restrictionDescription,leadIcon, + { if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ myDpm.getUserRestrictions(myComponent).getBoolean(restriction) }else{ false } }, + { + try{ + if(it){ + myDpm.addUserRestriction(myComponent,restriction) + }else{ + myDpm.clearUserRestriction(myComponent,restriction) + } + }catch(e:SecurityException){ + if(isProfileOwner(myDpm)){ + Toast.makeText(myContext, myContext.getString(R.string.require_device_owner), Toast.LENGTH_SHORT).show() } } - } - if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)|| isProfileOwner(myDpm))){ - strictState = myDpm.getUserRestrictions(myComponent).getBoolean(restriction) - } - if(VERSION.SDK_INT>=24){ - Switch( - checked = strictState, - onCheckedChange = { - strictState=it - try{ - if(strictState){ - myDpm.addUserRestriction(myComponent,restriction) - }else{ - myDpm.clearUserRestriction(myComponent,restriction) - } - }catch(e:SecurityException){ - if(isProfileOwner(myDpm)){ - Toast.makeText(myContext, "需要DeviceOwner", Toast.LENGTH_SHORT).show() - } - } - strictState = myDpm.getUserRestrictions(myComponent).getBoolean(restriction) - }, - enabled = isDeviceOwner(myDpm)|| isProfileOwner(myDpm), - modifier = Modifier.padding(end = 5.dp) - ) - } - } + }, + isDeviceOwner(myDpm)||isProfileOwner(myDpm) + ) } private class RestrictionData{ @@ -238,10 +246,10 @@ private class RestrictionData{ } fun application(myContext: Context):List{ val list:MutableList = mutableListOf() - list += Restriction(UserManager.DISALLOW_INSTALL_APPS,R.string.install_apps,"",R.drawable.android_fill0) + list += Restriction(UserManager.DISALLOW_INSTALL_APPS,R.string.install_app,"",R.drawable.android_fill0) if(VERSION.SDK_INT>=29){list += Restriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,R.string.install_unknown_src_globally,"",R.drawable.android_fill0)} list += Restriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,R.string.inst_unknown_src,"",R.drawable.android_fill0) - list += Restriction(UserManager.DISALLOW_UNINSTALL_APPS,R.string.uninstall_apps,"",R.drawable.delete_fill0) + list += Restriction(UserManager.DISALLOW_UNINSTALL_APPS,R.string.uninstall_app,"",R.drawable.delete_fill0) list += Restriction(UserManager.DISALLOW_APPS_CONTROL,R.string.apps_ctrl, myContext.getString(R.string.apps_control_desc),R.drawable.apps_fill0) if(VERSION.SDK_INT>=34){ list += Restriction(UserManager.DISALLOW_CONFIG_DEFAULT_APPS,R.string.config_default_apps,"",R.drawable.apps_fill0) } return list diff --git a/app/src/main/java/com/binbin/androidowner/ui/Animations.kt b/app/src/main/java/com/binbin/androidowner/ui/Animations.kt index c601dc5..cc3d80a 100644 --- a/app/src/main/java/com/binbin/androidowner/ui/Animations.kt +++ b/app/src/main/java/com/binbin/androidowner/ui/Animations.kt @@ -1,22 +1,22 @@ -package com.binbin.androidowner.ui.theme +package com.binbin.androidowner.ui -import android.content.Context import androidx.compose.animation.* import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.Spring import androidx.compose.animation.core.VisibilityThreshold import androidx.compose.animation.core.spring import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize import androidx.navigation.NavBackStackEntry +import com.binbin.androidowner.displayMetrics -class Animations(myContext: Context){ +class Animations{ private val fade: FiniteAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow) private val spring:FiniteAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold) - val navIconEnterTransition:EnterTransition = expandHorizontally() + fadeIn() - val navIconExitTransition:ExitTransition = shrinkHorizontally() + fadeOut() + val animateListSize:FiniteAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold) - private val screenWidth = myContext.resources.displayMetrics.widthPixels + private val screenWidth = displayMetrics.widthPixels private val initialOffsetValue = screenWidth/8 private val targetOffsetValue = screenWidth/8 diff --git a/app/src/main/java/com/binbin/androidowner/ui/Components.kt b/app/src/main/java/com/binbin/androidowner/ui/Components.kt index bef6de3..1804215 100644 --- a/app/src/main/java/com/binbin/androidowner/ui/Components.kt +++ b/app/src/main/java/com/binbin/androidowner/ui/Components.kt @@ -1,25 +1,66 @@ package com.binbin.androidowner.ui import android.content.Context +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Checkbox +import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.binbin.androidowner.R +@Composable +fun SubPageItem( + @StringRes title: Int, + desc:String, + operation: () -> Unit +){ + Row( + modifier = Modifier.fillMaxWidth().clickable(onClick = operation).padding(vertical = 15.dp) + ){ + Spacer(Modifier.fillMaxWidth(0.07F)) + Column { + Text(text = stringResource(title), style = typography.titleLarge) + if(desc!=""){Text(text = desc, color = colorScheme.onBackground.copy(alpha = 0.8F))} + } + } +} + +@Composable +fun NavIcon(operation: () -> Unit){ + Icon( + painter = painterResource(R.drawable.arrow_back_fill0), + contentDescription = "Back arrow", + modifier = Modifier + .padding(horizontal = 6.dp) + .clip(RoundedCornerShape(50)) + .clickable{ operation() } + .padding(5.dp) + ) +} + +@Composable +fun Information(content: @Composable ()->Unit){ + Column(modifier = Modifier.fillMaxWidth().padding(start = 5.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)) + content() + } + } +} @Composable fun RadioButtonItem( @@ -65,3 +106,33 @@ fun CheckBoxItem( Text(text = text, style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}, color = textColor, modifier = Modifier.padding(bottom = 2.dp)) } } + +@Composable +fun SwitchItem( + @StringRes title: Int, + desc: String, + @DrawableRes icon: Int?, + getState: ()->Boolean, + onCheckedChange: (Boolean)->Unit, + enable:Boolean=true +){ + var checked by remember{mutableStateOf(false)} + checked = getState() + Box(modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.align(Alignment.CenterStart)){ + Spacer(Modifier.fillMaxWidth(0.07F)) + if(icon!=null){ + Icon(painter = painterResource(icon),contentDescription = null) + Spacer(Modifier.fillMaxWidth(0.08F)) + } + Column(modifier = Modifier.padding(end = 60.dp)){ + Text(text = stringResource(title), style = typography.titleLarge) + if(desc!=""){Text(text = desc, color = colorScheme.onBackground.copy(alpha = 0.8F))}else{Spacer(Modifier.padding(vertical = 1.dp))} + } + } + Switch( + checked = checked, onCheckedChange = {onCheckedChange(it);checked=getState()}, + modifier = Modifier.align(Alignment.CenterEnd).padding(end = 12.dp), enabled = enable + ) + } +} diff --git a/app/src/main/res/drawable/arrow_back_fill0.xml b/app/src/main/res/drawable/arrow_back_fill0.xml new file mode 100644 index 0000000..1d4548b --- /dev/null +++ b/app/src/main/res/drawable/arrow_back_fill0.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 668f403..daa3de5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,7 +16,7 @@ 已激活 未激活 激活 - 撤销 + 停用 黑名单 白名单 允许 @@ -40,13 +40,18 @@ 功能开发中 请再试一次 + 效果未知 + 选项 + Device admin + Profile owner + Device owner 激活Device admin 点击查看激活命令 - adb shell dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver - adb shell dpm set-profile-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver - adb shell dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver + adb shell dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver + adb shell dpm set-profile-owner com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver + adb shell dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver 设备信息 企业资产 : %1$s 设备策略管理器角色(DPMRH):%1$s @@ -59,14 +64,15 @@ 设备唯一标识码 需要设置组织ID 组织名称 - 不受控制的账号类型 + 不受管理的账号 账号类型 转移所有权 把Device owner或Profile owner权限转移到另一个应用。目标必须是Device admin 目标包名 目标类名 - 锁屏信息 - 提供支持的短消息 + 锁屏提示信息 + 提供支持的消息 + 提供支持的消息 如果你禁用了某个功能,用户尝试使用这个功能时会看见这个消息(可多行) 消息 提供支持的长消息 @@ -123,6 +129,7 @@ MTE策略 MTE:内存标记拓展,安卓14和ARMv9的高端功能 附近App共享 + 附近共享策略 附近通知共享 在足够安全时启用 锁定任务功能 @@ -163,17 +170,18 @@ 开始时间 结束时间 请输入一天中的分钟(0~1440) + 系统更新接收时间: %1$s 网络 - 要求最小WiFi安全等级 + 最小WiFi安全等级 开放 优先网络服务 WiFi锁定 WiFi SSID策略 SSID列表: 不能为空 - 重复 + 已经存在 请选择策略 私人DNS 指定主机名 @@ -212,13 +220,16 @@ 工作资料 + Profile owner(工作资料) + 工作资料已激活 信息 已是工作资料 + 创建工作资料 可以创建工作资料:%1$s Device owner不能创建工作资料 由组织拥有的工作资料:%1$s 组织拥有的工作资料 - adb shell \"dpm mark-profile-owner-on-organization-owned-device --user %1$s com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver\" + adb shell \"dpm mark-profile-owner-on-organization-owned-device --user %1$s com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver\" 成为组织拥有的工作资料 跳过加密 创建 @@ -275,6 +286,13 @@ 用户限制 + 网络和互联网 + 更多连接 + 应用 + 用户 + 媒体 + 其他 + 需要DeviceOwner 配置移动数据 配置Wi-Fi 数据漫游 @@ -304,10 +322,8 @@ 包括TF卡和USB-OTG 打印 - 安装应用 安装未知来源应用(全局) 安装未知来源应用 - 卸载应用 控制应用 清空缓存/清空内部存储 修改默认App @@ -348,10 +364,49 @@ 用户管理 - + 修改用户名 + 用户会话开始消息 + 用户会话结束消息 + 用户信息 + 用户已解锁:%1$s + 支持多用户:%1$s + 系统用户:%1$s + 管理员用户:%1$s + 无头系统用户: %1$s + 用户可以退出 : %1$s + 临时用户: %1$s + 附属用户: %1$s + 当前UserID:%1$s + 当前用户序列号:%1$s + 用户操作 + 序列号 + 使用UID + 登出当前用户 + 在后台启动 + 切换 + 停止 + 移除 + 创建用户 + 用户名 + 跳过创建用户向导 + 临时用户 + 启用所有系统应用 + 新用户的序列号:%1$s + 附属用户ID + 有空字符串 + 如果多用户,附属用户ID相同时可以让其他用户附属于主用户 + 更换用户头像 + 尽量选择正方形的图片,以免产生问题 + 使用文件选择器而不是相册 + 选择图片... + 未知结果(失败) + 失败:受管理的资料 + 失败:当前用户 + 用户会话消息 密码与锁屏 + 密码信息 留空可以清除密码,纯数字将使用PIN码 最大密码错误次数 达到该限制会恢复出厂设置