This commit is contained in:
BinTianqi
2024-03-10 17:49:32 +08:00
parent 7bd9eaaa46
commit d7ed1b2071
18 changed files with 2552 additions and 1900 deletions

View File

@@ -769,6 +769,8 @@ Profile owner无法禁用部分功能工作资料中部分功能无效wear
打开开关就是禁用对应的功能,默认情况下所有开关都是关闭的
所有的用户限制都需要API24或以上
### 网络与互联网
- 配置移动数据

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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))
}
}

View File

@@ -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<Any>.toText():String{
var output = ""
var isFirst = true
for(each in listIterator()){
if(isFirst){isFirst=false}else{output+="\n"}
output+=each
}
return output
}
fun Set<Any>.toText():String{
var output = ""
var isFirst = true
for(each in iterator()){
if(isFirst){isFirst=false}else{output+="\n"}
output+=each
}
return output
}

View File

@@ -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<String>()
private var keepUninstallPkg = mutableListOf<String>()
private var permittedIme = mutableListOf<String>()
private var permittedAccessibility = mutableListOf<String>()
@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<String>
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

View File

@@ -7,7 +7,6 @@ import androidx.activity.result.ActivityResultLauncher
lateinit var getCaCert: ActivityResultLauncher<Intent>
lateinit var createUser: ActivityResultLauncher<Intent>
lateinit var createManagedProfile: ActivityResultLauncher<Intent>
lateinit var getApk: ActivityResultLauncher<Intent>
lateinit var getUserIcon: ActivityResultLauncher<Intent>

View File

@@ -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))
}
}
}

View File

@@ -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<WifiSsid>()
@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))
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)}

View File

@@ -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<String>()
@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->"未知"
}
}

View File

@@ -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<Restriction>{
val list:MutableList<Restriction> = 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

View File

@@ -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<Float> = spring(stiffness = Spring.StiffnessMediumLow)
private val spring:FiniteAnimationSpec<IntOffset> = spring(stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold)
val navIconEnterTransition:EnterTransition = expandHorizontally() + fadeIn()
val navIconExitTransition:ExitTransition = shrinkHorizontally() + fadeOut()
val animateListSize:FiniteAnimationSpec<IntSize> = 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

View File

@@ -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
)
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="m313,520 l224,224 -57,56 -320,-320 320,-320 57,56 -224,224h487v80L313,520Z"/>
</vector>

View File

@@ -16,7 +16,7 @@
<string name="activated">已激活</string>
<string name="deactivated">未激活</string>
<string name="activate">激活</string>
<string name="deactivate">撤销</string>
<string name="deactivate">停用</string>
<string name="blacklist">黑名单</string>
<string name="whitelist">白名单</string>
<string name="granted">允许</string>
@@ -40,13 +40,18 @@
<string name="developing">功能开发中</string>
<string name="place_holder" />
<string name="try_again">请再试一次</string>
<string name="unknown_effect">效果未知</string>
<string name="options">选项</string>
<!--Permissions-->
<string name="device_admin">Device admin</string>
<string name="profile_owner">Profile owner</string>
<string name="device_owner">Device owner</string>
<string name="activate_device_admin">激活Device admin</string>
<string name="touch_to_view_command">点击查看激活命令</string>
<string name="activate_device_admin_command">adb shell dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver</string>
<string name="activate_profile_owner_command">adb shell dpm set-profile-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver</string>
<string name="activate_device_owner_command">adb shell dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver</string>
<string name="activate_device_admin_command">adb shell dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver</string>
<string name="activate_profile_owner_command">adb shell dpm set-profile-owner com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver</string>
<string name="activate_device_owner_command">adb shell dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver</string>
<string name="device_info">设备信息</string>
<string name="is_device_financed">企业资产 : %1$s</string>
<string name="dpmrh">设备策略管理器角色(DPMRH)%1$s</string>
@@ -59,14 +64,15 @@
<string name="enrollment_specific_id">设备唯一标识码</string>
<string name="require_set_org_id">需要设置组织ID</string>
<string name="org_name">组织名称</string>
<string name="account_types_management_disabled">不受控制的账号类型</string>
<string name="account_types_management_disabled">不受管理的账号</string>
<string name="account_types">账号类型</string>
<string name="transform_ownership">转移所有权</string>
<string name="transform_ownership_desc">把Device owner或Profile owner权限转移到另一个应用。目标必须是Device admin</string>
<string name="target_package_name">目标包名</string>
<string name="target_class_name">目标类名</string>
<string name="owner_lockscr_info">锁屏信息</string>
<string name="support_msg">提供支持的消息</string>
<string name="owner_lockscr_info">锁屏提示信息</string>
<string name="support_msg">提供支持的消息</string>
<string name="short_support_msg">提供支持的消息</string>
<string name="support_msg_desc">如果你禁用了某个功能,用户尝试使用这个功能时会看见这个消息(可多行)</string>
<string name="message">消息</string>
<string name="long_support_msg">提供支持的长消息</string>
@@ -123,6 +129,7 @@
<string name="mte_policy">MTE策略</string>
<string name="mte_policy_desc">MTE内存标记拓展安卓14和ARMv9的高端功能</string>
<string name="nearby_app_streaming">附近App共享</string>
<string name="nearby_streaming_policy">附近共享策略</string>
<string name="nearby_notifi_streaming">附近通知共享</string>
<string name="enable_if_secure_enough">在足够安全时启用</string>
<string name="lock_task_feature">锁定任务功能</string>
@@ -163,17 +170,18 @@
<string name="start_time">开始时间</string>
<string name="end_time">结束时间</string>
<string name="minutes_in_one_day">请输入一天中的分钟0~1440</string>
<string name="update_received_time">系统更新接收时间: %1$s</string>
<!--Network-->
<string name="network">网络</string>
<string name="min_wifi_security_level">要求最小WiFi安全等级</string>
<string name="min_wifi_security_level">最小WiFi安全等级</string>
<string name="wifi_security_level_open">开放</string>
<string name="preferential_network_service">优先网络服务</string>
<string name="wifi_lockdown">WiFi锁定</string>
<string name="wifi_ssid_policy">WiFi SSID策略</string>
<string name="ssid_list_is">SSID列表</string>
<string name="cannot_be_empty">不能为空</string>
<string name="already_exist">重复</string>
<string name="already_exist">已经存在</string>
<string name="please_select_a_policy">请选择策略</string>
<string name="private_dns">私人DNS</string>
<string name="dns_provide_hostname">指定主机名</string>
@@ -212,13 +220,16 @@
<!--WorkProfile-->
<string name="work_profile">工作资料</string>
<string name="work_profile_owner">Profile owner工作资料</string>
<string name="work_profile_activated">工作资料已激活</string>
<string name="info">信息</string>
<string name="is_already_work_profile">已是工作资料</string>
<string name="create_work_profile">创建工作资料</string>
<string name="able_to_create_work_profile">可以创建工作资料:%1$s</string>
<string name="device_owner_cannot_create_work_profile">Device owner不能创建工作资料</string>
<string name="is_org_owned_profile">由组织拥有的工作资料:%1$s</string>
<string name="org_owned_work_profile">组织拥有的工作资料</string>
<string name="activate_org_profile_command" tools:ignore="TypographyDashes">adb shell \"dpm mark-profile-owner-on-organization-owned-device --user %1$s com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver\"</string>
<string name="activate_org_profile_command" tools:ignore="TypographyDashes">adb shell \"dpm mark-profile-owner-on-organization-owned-device --user %1$s com.binbin.androidowner/com.binbin.androidowner.dpm.MyDeviceAdminReceiver\"</string>
<string name="become_org_profile">成为组织拥有的工作资料</string>
<string name="skip_encryption">跳过加密</string>
<string name="create">创建</string>
@@ -275,6 +286,13 @@
<!--UserRestriction-->
<string name="user_restrict">用户限制</string>
<string name="network_internet">网络和互联网</string>
<string name="more_connectivity">更多连接</string>
<string name="applications">应用</string>
<string name="users">用户</string>
<string name="media">媒体</string>
<string name="other">其他</string>
<string name="require_device_owner">需要DeviceOwner</string>
<string name="config_mobile_network">配置移动数据</string>
<string name="config_wifi">配置Wi-Fi</string>
<string name="data_roaming">数据漫游</string>
@@ -304,10 +322,8 @@
<string name="mount_physical_media_desc">包括TF卡和USB-OTG</string>
<string name="printing">打印</string>
<string name="install_apps">安装应用</string>
<string name="install_unknown_src_globally">安装未知来源应用(全局)</string>
<string name="inst_unknown_src">安装未知来源应用</string>
<string name="uninstall_apps">卸载应用</string>
<string name="apps_ctrl">控制应用</string>
<string name="apps_control_desc">清空缓存/清空内部存储</string>
<string name="config_default_apps">修改默认App</string>
@@ -348,10 +364,49 @@
<!--UserManage-->
<string name="user_manage">用户管理</string>
<string name="edit_username">修改用户名</string>
<string name="start_user_session_msg">用户会话开始消息</string>
<string name="end_user_session_msg">用户会话结束消息</string>
<string name="user_info">用户信息</string>
<string name="is_user_unlocked">用户已解锁:%1$s</string>
<string name="is_support_multi_user">支持多用户:%1$s</string>
<string name="is_system_user">系统用户:%1$s</string>
<string name="is_admin_user">管理员用户:%1$s</string>
<string name="is_headless_system_user">无头系统用户: %1$s</string>
<string name="user_can_logout">用户可以退出 : %1$s</string>
<string name="is_ephemeral_user">临时用户: %1$s</string>
<string name="is_affiliated_user">附属用户: %1$s</string>
<string name="user_id_is">当前UserID%1$s</string>
<string name="user_serial_number_is">当前用户序列号:%1$s</string>
<string name="user_operation">用户操作</string>
<string name="serial_number">序列号</string>
<string name="use_uid">使用UID</string>
<string name="logout_current_user">登出当前用户</string>
<string name="start_in_background">在后台启动</string>
<string name="user_operation_switch">切换</string>
<string name="user_operation_stop">停止</string>
<string name="user_operation_remove">移除</string>
<string name="create_user">创建用户</string>
<string name="username">用户名</string>
<string name="create_user_skip_wizard">跳过创建用户向导</string>
<string name="create_user_ephemeral_user">临时用户</string>
<string name="create_user_enable_all_system_app">启用所有系统应用</string>
<string name="serial_number_of_new_user_is">新用户的序列号:%1$s</string>
<string name="affiliation_id">附属用户ID</string>
<string name="include_empty_string">有空字符串</string>
<string name="affiliation_id_desc">如果多用户附属用户ID相同时可以让其他用户附属于主用户</string>
<string name="change_user_icon">更换用户头像</string>
<string name="pick_a_rect_picture">尽量选择正方形的图片,以免产生问题</string>
<string name="file_picker_instead_gallery">使用文件选择器而不是相册</string>
<string name="select_picture" tools:ignore="TypographyEllipsis">选择图片...</string>
<string name="unknown_result">未知结果(失败)</string>
<string name="fail_managed_profile">失败:受管理的资料</string>
<string name="fail_current_user">失败:当前用户</string>
<string name="user_session_msg">用户会话消息</string>
<!--Password&Keyguard-->
<string name="password_and_keyguard">密码与锁屏</string>
<string name="password_info">密码信息</string>
<string name="reset_pwd_desc">留空可以清除密码纯数字将使用PIN码</string>
<string name="max_pwd_fail">最大密码错误次数</string>
<string name="max_pwd_fail_desc">达到该限制会恢复出厂设置</string>