Rename project

from 'Android Owner' to 'OwnDroid'
resolve #2
This commit is contained in:
BinTianqi
2024-04-21 09:11:16 +08:00
parent c0df796610
commit 26793f510b
38 changed files with 265 additions and 224 deletions

View File

@@ -0,0 +1,906 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
import android.app.admin.PackagePolicy
import android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST
import android.app.admin.PackagePolicy.PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM
import android.app.admin.PackagePolicy.PACKAGE_POLICY_BLOCKLIST
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager.NameNotFoundException
import android.net.Uri
import android.os.Build.VERSION
import android.os.Looper
import android.provider.Settings
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
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.painterResource
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.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.bintianqi.owndroid.*
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.ui.*
import com.bintianqi.owndroid.ui.theme.bgColor
import kotlinx.coroutines.delay
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.Executors
private var credentialList = mutableSetOf<String>()
private var crossProfilePkg = mutableSetOf<String>()
private var keepUninstallPkg = mutableListOf<String>()
private var permittedIme = mutableListOf<String>()
private var permittedAccessibility = mutableListOf<String>()
@Composable
fun ApplicationManage(navCtrl:NavHostController){
val focusMgr = LocalFocusManager.current
var pkgName by rememberSaveable{ mutableStateOf("") }
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)
)*/
TopBar(backStackEntry, navCtrl, localNavCtrl){Text(text = stringResource(titleMap[backStackEntry?.destination?.route] ?: R.string.app_manager))}
}
){ paddingValues->
Column(modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding())){
LaunchedEffect(Unit) {
while(true){
if(applySelectedPackage){ pkgName = selectedPackage; applySelectedPackage = false; applySelectedPermission = true}
delay(100)
}
}
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()}),
trailingIcon = {
Icon(painter = painterResource(R.drawable.checklist_fill0), contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable(onClick = {navCtrl.navigate("PackageSelector")})
.padding(3.dp))
},
singleLine = true
)
}
NavHost(
navController = localNavCtrl, startDestination = "Home",
enterTransition = Animations().navHostEnterTransition,
exitTransition = Animations().navHostExitTransition,
popEnterTransition = Animations().navHostPopEnterTransition,
popExitTransition = Animations().navHostPopExitTransition,
modifier = Modifier.background(bgColor)
){
composable(route = "Home"){Home(localNavCtrl,pkgName)}
composable(route = "BlockUninstall"){BlockUninstall(pkgName)}
composable(route = "UserControlDisabled"){UserCtrlDisabledPkg(pkgName)}
composable(route = "PermissionManage"){PermissionManage(pkgName,navCtrl)}
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)}
}
}
}
}
@Composable
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, Receiver::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())
}
SubPageItem(R.string.app_info,"",R.drawable.open_in_new){
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,"",R.drawable.block_fill0,
{
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),R.drawable.visibility_off_fill0,
{myDpm.isApplicationHidden(myComponent,pkgName)},{myDpm.setApplicationHidden(myComponent, pkgName, it)}
)
}
if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){
SwitchItem(
R.string.always_on_vpn,"",R.drawable.vpn_key_fill0,{pkgName == myDpm.getAlwaysOnVpnPackage(myComponent)},
{
try {
myDpm.setAlwaysOnVpnPackage(myComponent, pkgName, it)
} catch(e: 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,"",R.drawable.delete_forever_fill0){navCtrl.navigate("BlockUninstall")}
}
if(VERSION.SDK_INT>=30&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){
SubPageItem(R.string.ucd,"",R.drawable.do_not_touch_fill0){navCtrl.navigate("UserControlDisabled")}
}
if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){
SubPageItem(R.string.permission_manage,"",R.drawable.key_fill0){navCtrl.navigate("PermissionManage")}
}
if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){
SubPageItem(R.string.cross_profile_package,"",R.drawable.work_fill0){navCtrl.navigate("CrossProfilePackage")}
}
if(isProfileOwner(myDpm)){
SubPageItem(R.string.cross_profile_widget,"",R.drawable.widgets_fill0){navCtrl.navigate("CrossProfileWidget")}
}
if(VERSION.SDK_INT>=34&&isDeviceOwner(myDpm)){
SubPageItem(R.string.credential_manage_policy,"",R.drawable.license_fill0){navCtrl.navigate("CredentialManagePolicy")}
}
if(isProfileOwner(myDpm)||isDeviceOwner(myDpm)){
SubPageItem(R.string.permitted_accessibility_app,"",R.drawable.settings_accessibility_fill0){navCtrl.navigate("Accessibility")}
}
if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){
SubPageItem(R.string.permitted_ime,"",R.drawable.keyboard_fill0){navCtrl.navigate("IME")}
}
if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){
SubPageItem(R.string.keep_uninstalled_pkgs,"",R.drawable.delete_fill0){navCtrl.navigate("KeepUninstalled")}
}
if(VERSION.SDK_INT>=28){
SubPageItem(R.string.clear_app_data,"",R.drawable.mop_fill0){navCtrl.navigate("ClearAppData")}
}
SubPageItem(R.string.install_app,"",R.drawable.install_mobile_fill0){navCtrl.navigate("InstallApp")}
SubPageItem(R.string.uninstall_app,"",R.drawable.delete_fill0){navCtrl.navigate("UninstallApp")}
if(VERSION.SDK_INT>=34){
SubPageItem(R.string.set_default_dialer,"",R.drawable.call_fill0){navCtrl.navigate("DefaultDialer")}
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@SuppressLint("NewApi")
@Composable
private fun UserCtrlDisabledPkg(pkgName:String){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
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 = pkgList.toText()
}
var inited by remember{mutableStateOf(false)}
if(!inited){refresh();inited=true}
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.ucd), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
Text(text = stringResource(R.string.ucd_desc))
Spacer(Modifier.padding(vertical = 5.dp))
Text(text = stringResource(R.string.app_list_is))
SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(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 = {
if(pkgName!=""){
pkgList.add(pkgName)
myDpm.setUserControlDisabledPackages(myComponent,pkgList)
refresh()
}else{
Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth(0.49F)
){
Text(stringResource(R.string.add))
}
Button(
onClick = {
val result = if(pkgName!=""){pkgList.remove(pkgName)}else{false}
if(result){
myDpm.setUserControlDisabledPackages(myComponent,pkgList)
refresh()
}else{
Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth(0.96F)
){
Text(stringResource(R.string.remove))
}
}
Button(
onClick = { myDpm.setUserControlDisabledPackages(myComponent, listOf()); refresh() },
modifier = Modifier.fillMaxWidth()
){
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,Receiver::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
private fun PermissionManage(pkgName: String, navCtrl: NavHostController){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
var inputPermission by remember{mutableStateOf(selectedPermission)}
var currentState by remember{mutableStateOf(myContext.getString(R.string.unknown))}
val grantState = mapOf(
PERMISSION_GRANT_STATE_DEFAULT to stringResource(R.string.decide_by_user),
PERMISSION_GRANT_STATE_GRANTED to stringResource(R.string.granted),
PERMISSION_GRANT_STATE_DENIED to stringResource(R.string.denied)
)
LaunchedEffect(Unit) {
while(true){
if(applySelectedPermission){inputPermission = selectedPermission; applySelectedPermission = false}
delay(100)
}
}
LaunchedEffect(pkgName) {
if(pkgName!=""){currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)]!!}
}
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
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; selectedPermission = inputPermission
currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)]!!
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth(),
trailingIcon = {
Icon(painter = painterResource(R.drawable.checklist_fill0), contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable(onClick = {navCtrl.navigate("PermissionPicker")})
.padding(3.dp))
}
)
Spacer(Modifier.padding(vertical = 5.dp))
Text(stringResource(R.string.current_state, currentState))
Spacer(Modifier.padding(vertical = 5.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){
Button(
onClick = {
myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_GRANTED)
currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)]!!
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.grant))
}
Button(
onClick = {
myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_DENIED)
currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)]!!
},
Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.deny))
}
}
Button(
onClick = {
myDpm.setPermissionGrantState(myComponent,pkgName,inputPermission, PERMISSION_GRANT_STATE_DEFAULT)
currentState = grantState[myDpm.getPermissionGrantState(myComponent,pkgName,inputPermission)]!!
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.decide_by_user))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@SuppressLint("NewApi")
@Composable
private fun CrossProfilePkg(pkgName: String){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.cross_profile_package), style = typography.headlineLarge)
var list by remember{mutableStateOf("")}
val refresh = {
crossProfilePkg = myDpm.getCrossProfilePackages(myComponent)
list = crossProfilePkg.toText()
}
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){
Button(
onClick = {
if(pkgName!=""){
crossProfilePkg.add(pkgName)}
myDpm.setCrossProfilePackages(myComponent, crossProfilePkg)
refresh()
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.add))
}
Button(
onClick = {
if(pkgName!=""){
crossProfilePkg.remove(pkgName)}
myDpm.setCrossProfilePackages(myComponent, crossProfilePkg)
refresh()
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.remove))
}
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
private fun CrossProfileWidget(pkgName: String){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
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 = pkgList.toText()
}
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.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 = {
if(pkgName!=""){myDpm.addCrossProfileWidgetProvider(myComponent,pkgName)}
refresh()
},
modifier = Modifier.fillMaxWidth(0.49F)
){
Text(stringResource(R.string.add))
}
Button(
onClick = {
if(pkgName!=""){myDpm.removeCrossProfileWidgetProvider(myComponent,pkgName)}
refresh()
},
modifier = Modifier.fillMaxWidth(0.96F)
){
Text(stringResource(R.string.remove))
}
}
Spacer(Modifier.padding(vertical = 10.dp))
}
}
@SuppressLint("NewApi")
@Composable
private fun CredentialManagePolicy(pkgName: String){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val focusMgr = LocalFocusManager.current
var policy:PackagePolicy?
var policyType by remember{mutableIntStateOf(-1)}
var credentialListText by remember{mutableStateOf("")}
val refreshPolicy = {
policy = myDpm.credentialManagerPolicy
policyType = policy?.policyType ?: -1
credentialList = policy?.packageNames ?: mutableSetOf()
credentialList = credentialList.toMutableSet()
}
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(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)}
credentialListText = credentialList.toText()
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.add))
}
Button(
onClick = {
if(pkgName!=""){
credentialList.remove(pkgName)}
credentialListText = credentialList.toText()
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.remove))
}
}
}
}
Button(
onClick = {
focusMgr.clearFocus()
try{
if(policyType!=-1&&credentialList.isNotEmpty()){
myDpm.credentialManagerPolicy = PackagePolicy(policyType, credentialList)
}else{
myDpm.credentialManagerPolicy = null
}
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
}catch(e:java.lang.IllegalArgumentException){
Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show()
}finally {
refreshPolicy()
credentialListText = credentialList.toText()
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.apply))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
private fun PermittedAccessibility(pkgName: String){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.permitted_accessibility_app), style = typography.headlineLarge)
var listText by remember{ mutableStateOf("") }
LaunchedEffect(Unit){
val getList = myDpm.getPermittedAccessibilityServices(myComponent)
if(getList!=null){ permittedAccessibility = getList }
listText = permittedAccessibility.toText()
}
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); listText = permittedAccessibility.toText()},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.add))
}
Button(
onClick = { permittedAccessibility.remove(pkgName); listText = permittedAccessibility.toText() },
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.remove))
}
}
Button(
onClick = {
focusMgr.clearFocus()
Toast.makeText(myContext, if(myDpm.setPermittedAccessibilityServices(myComponent, permittedAccessibility)){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show()
val getList = myDpm.getPermittedAccessibilityServices(myComponent)
if(getList!=null){ permittedAccessibility = getList }
listText = permittedAccessibility.toText()
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.apply))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
private fun PermittedIME(pkgName: String){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.permitted_ime), style = typography.headlineLarge)
var imeListText by remember{ mutableStateOf("") }
LaunchedEffect(Unit){
val getList = myDpm.getPermittedInputMethods(myComponent)
if(getList!=null){ permittedIme = getList }
imeListText = permittedIme.toText()
}
Spacer(Modifier.padding(vertical = 5.dp))
Text(text = stringResource(R.string.app_list_is))
SelectionContainer(modifier = Modifier.horizontalScroll(rememberScrollState()).animateContentSize(scrollAnim())){
Text(text = if(imeListText==""){stringResource(R.string.none)}else{imeListText})
}
Spacer(Modifier.padding(vertical = 5.dp))
Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween){
Button(
onClick = { permittedIme.add(pkgName); imeListText = permittedIme.toText() },
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.add))
}
Button(
onClick = { permittedIme.remove(pkgName); imeListText = permittedIme.toText() },
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.remove))
}
}
Button(
onClick = {
focusMgr.clearFocus()
Toast.makeText(myContext, if(myDpm.setPermittedInputMethods(myComponent, permittedIme)){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show()
val getList = myDpm.getPermittedInputMethods(myComponent)
if(getList!=null){ permittedIme = getList }
imeListText = permittedIme.toText()
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.apply))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@SuppressLint("NewApi")
@Composable
private fun KeepUninstalledApp(pkgName: String){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.keep_uninstalled_pkgs), style = typography.headlineLarge)
var listText by remember{mutableStateOf("")}
LaunchedEffect(Unit){
val getList = myDpm.getKeepUninstalledPackages(myComponent)
if(getList!=null){ keepUninstallPkg = getList }
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)
listText = keepUninstallPkg.toText()
},
modifier = Modifier.fillMaxWidth(0.49F)
){
Text(stringResource(R.string.add))
}
Button(
onClick = {
keepUninstallPkg.remove(pkgName)
listText = keepUninstallPkg.toText()
},
modifier = Modifier.fillMaxWidth(0.96F)
){
Text(stringResource(R.string.remove))
}
}
Button(
onClick = {
focusMgr.clearFocus()
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
private fun UninstallApp(pkgName: String){
val myContext = LocalContext.current
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)
val intentSender = PendingIntent.getBroadcast(myContext, 8, intent, PendingIntent.FLAG_IMMUTABLE).intentSender
val pkgInstaller = myContext.packageManager.packageInstaller
pkgInstaller.uninstall(pkgName, intentSender)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.silent_uninstall))
}
Button(
onClick = {
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE)
intent.setData(Uri.parse("package:$pkgName"))
myContext.startActivity(intent)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.request_uninstall))
}
}
}
}
@Composable
private fun InstallApp(){
val myContext = LocalContext.current
val focusMgr = LocalFocusManager.current
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()
val installApkIntent = Intent(Intent.ACTION_GET_CONTENT)
installApkIntent.setType("application/vnd.android.package-archive")
installApkIntent.addCategory(Intent.CATEGORY_OPENABLE)
getApk.launch(installApkIntent)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.select_apk))
}
var selected by remember{mutableStateOf(false)}
LaunchedEffect(selected){while(true){ delay(800); selected = apkUri!=null}}
AnimatedVisibility(selected) {
Spacer(Modifier.padding(vertical = 3.dp))
Column(modifier = Modifier.fillMaxWidth()){
Button(
onClick = { uriToStream(myContext, apkUri){stream -> installPackage(myContext,stream)} },
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.silent_install))
}
Button(
onClick = {
val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
intent.setData(apkUri)
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
myContext.startActivity(intent)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.request_install))
}
}
}
}
}
@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,Receiver::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
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)
val out = session.openWrite("COSU", 0, -1)
val buffer = ByteArray(65536)
var c: Int
while(inputStream.read(buffer).also{c = it}!=-1) { out.write(buffer, 0, c) }
session.fsync(out)
inputStream.close()
out.close()
val pendingIntent = PendingIntent.getBroadcast(context, sessionId, Intent(context, PackageInstallerReceiver::class.java), PendingIntent.FLAG_IMMUTABLE).intentSender
session.commit(pendingIntent)
}

View File

@@ -0,0 +1,28 @@
package com.bintianqi.owndroid.dpm
import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.net.Uri
import androidx.activity.result.ActivityResultLauncher
var selectedPackage = ""
var applySelectedPackage = false
var selectedPermission = ""
var applySelectedPermission = false
lateinit var getCaCert: ActivityResultLauncher<Intent>
lateinit var createManagedProfile: ActivityResultLauncher<Intent>
lateinit var getApk: ActivityResultLauncher<Intent>
lateinit var getUserIcon: ActivityResultLauncher<Intent>
var userIconUri: Uri? = null
var apkUri: Uri? = null
var caCert = byteArrayOf()
fun isDeviceOwner(dpm: DevicePolicyManager): Boolean {
return dpm.isDeviceOwnerApp("com.bintianqi.owndroid")
}
fun isProfileOwner(dpm: DevicePolicyManager): Boolean {
return dpm.isProfileOwnerApp("com.bintianqi.owndroid")
}

View File

@@ -0,0 +1,289 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManager.*
import android.content.*
import android.os.Binder
import android.os.Build.VERSION
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.focusable
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.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.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.ui.*
import com.bintianqi.owndroid.ui.theme.bgColor
@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)
)*/
TopBar(backStackEntry,navCtrl, localNavCtrl)
}
){
NavHost(
navController = localNavCtrl, startDestination = "Home",
enterTransition = Animations().navHostEnterTransition,
exitTransition = Animations().navHostExitTransition,
popEnterTransition = Animations().navHostPopEnterTransition,
popExitTransition = Animations().navHostPopExitTransition,
modifier = Modifier.background(bgColor).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
private fun Home(navCtrl: NavHostController){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext, Receiver::class.java)
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){
Text(text = stringResource(R.string.work_profile), style = typography.headlineLarge, modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp))
if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){
SubPageItem(R.string.org_owned_work_profile,"",R.drawable.corporate_fare_fill0){navCtrl.navigate("OrgOwnedWorkProfile")}
}
if(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE))){
SubPageItem(R.string.create_work_profile,"",R.drawable.work_fill0){navCtrl.navigate("CreateWorkProfile")}
}
if(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile){
SubPageItem(R.string.suspend_personal_app,"",R.drawable.block_fill0){navCtrl.navigate("SuspendPersonalApp")}
}
if(isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)))){
SubPageItem(R.string.intent_filter,"",R.drawable.filter_alt_fill0){navCtrl.navigate("IntentFilter")}
}
if(VERSION.SDK_INT>=31&&(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent))){
SubPageItem(R.string.org_id,"",R.drawable.corporate_fare_fill0){navCtrl.navigate("OrgID")}
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
private fun CreateWorkProfile(){
val myContext = LocalContext.current
val myComponent = ComponentName(myContext,Receiver::class.java)
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 {
val intent = Intent(ACTION_PROVISION_MANAGED_PROFILE)
if(VERSION.SDK_INT>=23){
intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,myComponent)
}else{
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)}
createManagedProfile.launch(intent)
}catch(e:ActivityNotFoundException){
Toast.makeText(myContext,myContext.getString(R.string.unsupported),Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.create))
}
}
}
@SuppressLint("NewApi")
@Composable
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
)
}
CopyTextButton(myContext, R.string.copy_command, stringResource(R.string.activate_org_profile_command, Binder.getCallingUid()/100000))
}
}
}
@SuppressLint("NewApi")
@Composable
private fun OrgID(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val focusMgr = LocalFocusManager.current
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
var orgId by remember{mutableStateOf("")}
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()
)
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)
Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show()
},
enabled = orgId.length in 6..64,
modifier = Modifier.fillMaxWidth()
){
Text(stringResource(R.string.apply))
}
Spacer(Modifier.padding(vertical = 5.dp))
Information{Text(text = stringResource(R.string.get_specific_id_after_set_org_id))}
}
}
@SuppressLint("NewApi")
@Composable
private fun SuspendPersonalApp(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
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))
OutlinedTextField(
value = time, onValueChange = {time=it}, modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp),
label = {Text(stringResource(R.string.time_unit_ms))},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()})
)
Text(text = stringResource(R.string.cannot_less_than_72_hours))
Button(
onClick = {
myDpm.setManagedProfileMaximumTimeOff(myComponent,time.toLong())
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.apply))
}
}
}
@Composable
private fun IntentFilter(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
var action by remember{mutableStateOf("")}
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()
)
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter(action), FLAG_PARENT_CAN_ACCESS_MANAGED)
Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.add_intent_filter_work_to_personal))
}
Button(
onClick = {
myDpm.addCrossProfileIntentFilter(myComponent, IntentFilter(action), FLAG_MANAGED_CAN_ACCESS_PARENT)
Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.add_intent_filter_personal_to_work))
}
Spacer(Modifier.padding(vertical = 2.dp))
Button(
onClick = {
myDpm.clearCrossProfileIntentFilters(myComponent)
Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
){
Text(stringResource(R.string.clear_cross_profile_filters))
}
}
}

View File

@@ -0,0 +1,809 @@
package com.bintianqi.owndroid.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.net.wifi.WifiSsid
import android.os.Build.VERSION
import android.telephony.TelephonyManager
import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
import android.telephony.data.ApnSetting.*
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.*
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.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.alpha
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.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.bintianqi.owndroid.dpm.scrollAnim
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.toText
import com.bintianqi.owndroid.ui.*
import com.bintianqi.owndroid.ui.theme.bgColor
var ssidSet = mutableSetOf<WifiSsid>()
@Composable
fun Network(navCtrl: NavHostController){
val localNavCtrl = rememberNavController()
val backStackEntry by localNavCtrl.currentBackStackEntryAsState()
val scrollState = rememberScrollState()
/*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)
)*/
TopBar(backStackEntry,navCtrl,localNavCtrl){
if(backStackEntry?.destination?.route=="Home"&&scrollState.maxValue>80){
Text(
text = stringResource(R.string.network),
modifier = Modifier.alpha((maxOf(scrollState.value-30,0)).toFloat()/80)
)
}
}
}
){
NavHost(
navController = localNavCtrl, startDestination = "Home",
enterTransition = Animations().navHostEnterTransition,
exitTransition = Animations().navHostExitTransition,
popEnterTransition = Animations().navHostPopEnterTransition,
popExitTransition = Animations().navHostPopExitTransition,
modifier = Modifier.background(bgColor).padding(top = it.calculateTopPadding())
){
composable(route = "Home"){Home(localNavCtrl,scrollState)}
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,scrollState: ScrollState){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext, Receiver::class.java)
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)){
Text(text = stringResource(R.string.network), style = typography.headlineLarge, modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp))
if(VERSION.SDK_INT>=24&&isDeviceOwner(myDpm)){
val wifimac = myDpm.getWifiMacAddress(myComponent)
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,"",R.drawable.tune_fill0){navCtrl.navigate("Switches")}
}
if(VERSION.SDK_INT>=33){
SubPageItem(R.string.min_wifi_security_level,"",R.drawable.wifi_password_fill0){navCtrl.navigate("MinWifiSecurityLevel")}
}
if(VERSION.SDK_INT>=33&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){
SubPageItem(R.string.wifi_ssid_policy,"",R.drawable.wifi_fill0){navCtrl.navigate("WifiSsidPolicy")}
}
if(VERSION.SDK_INT>=29&&isDeviceOwner(myDpm)){
SubPageItem(R.string.private_dns,"",R.drawable.dns_fill0){navCtrl.navigate("PrivateDNS")}
}
if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)))){
SubPageItem(R.string.retrieve_net_logs,"",R.drawable.description_fill0){navCtrl.navigate("NetLog")}
}
if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){
SubPageItem(R.string.wifi_keypair,"",R.drawable.key_fill0){navCtrl.navigate("WifiKeypair")}
}
if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){
SubPageItem(R.string.apn_settings,"",R.drawable.cell_tower_fill0){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,Receiver::class.java)
Column(modifier = Modifier.fillMaxSize()){
Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT>=33&&isDeviceOwner(myDpm)){
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))){
SwitchItem(R.string.wifi_lockdown,"",R.drawable.wifi_password_fill0,
{myDpm.hasLockdownAdminConfiguredNetworks(myComponent)},{myDpm.setConfiguredNetworksLockdownState(myComponent,it)}
)
}
}
}
@SuppressLint("NewApi")
@Composable
private fun WifiSecLevel(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
var selectedWifiSecLevel by remember{mutableIntStateOf(myDpm.minimumRequiredWifiSecurityLevel)}
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 = {
myDpm.minimumRequiredWifiSecurityLevel=selectedWifiSecLevel
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
){
Text(stringResource(R.string.apply))
}
}
}
@SuppressLint("NewApi")
@Composable
private fun WifiSsidPolicy(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val focusMgr = LocalFocusManager.current
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("")}
var ssidList by remember{mutableStateOf("")}
val refreshPolicy = {
policy = myDpm.wifiSsidPolicy
selectedPolicyType = policy?.policyType ?: -1
ssidSet = policy?.ssids ?: mutableSetOf()
}
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()
)
Spacer(Modifier.padding(vertical = 5.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){
Button(
onClick = {
if(inputSsid==""){
Toast.makeText(myContext, myContext.getString(R.string.cannot_be_empty), Toast.LENGTH_SHORT).show()
}else if(WifiSsid.fromBytes(inputSsid.toByteArray()) in ssidSet){
Toast.makeText(myContext, myContext.getString(R.string.already_exist), Toast.LENGTH_SHORT).show()
}else{
ssidSet.add(WifiSsid.fromBytes(inputSsid.toByteArray()))
ssidList = ssidSet.toText()
}
inputSsid = ""
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.add))
}
Button(
onClick = {
if(inputSsid==""){
Toast.makeText(myContext, myContext.getString(R.string.cannot_be_empty), Toast.LENGTH_SHORT).show()
}else if(WifiSsid.fromBytes(inputSsid.toByteArray()) in ssidSet){
ssidSet.remove(WifiSsid.fromBytes(inputSsid.toByteArray()))
inputSsid = ""
ssidList = ssidSet.toText()
}else{
Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.remove))
}
}
Button(
onClick = {
focusMgr.clearFocus()
if(selectedPolicyType==-1){
if(policy==null&&ssidSet.isNotEmpty()){
Toast.makeText(myContext, myContext.getString(R.string.please_select_a_policy), Toast.LENGTH_SHORT).show()
}else{
myDpm.wifiSsidPolicy = null
refreshPolicy()
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
}
}else{
myDpm.wifiSsidPolicy = if(ssidSet.size==0){ null }else{ WifiSsidPolicy(selectedPolicyType, ssidSet) }
refreshPolicy()
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))
}
}
@SuppressLint("NewApi")
@Composable
private fun PrivateDNS(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
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(
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(
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)))
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 = 10.dp))
var inputHost by remember{mutableStateOf(myDpm.getGlobalPrivateDnsHost(myComponent) ?: "")}
OutlinedTextField(
value = inputHost,
onValueChange = {inputHost=it},
label = {Text(stringResource(R.string.dns_hostname))},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth()
)
Spacer(Modifier.padding(vertical = 3.dp))
Button(
onClick = {
focusMgr.clearFocus()
val result: Int
try{
result = myDpm.setGlobalPrivateDnsModeSpecifiedHost(myComponent,inputHost)
Toast.makeText(myContext, operationResult[result], Toast.LENGTH_SHORT).show()
}catch(e:IllegalArgumentException){
Toast.makeText(myContext, myContext.getString(R.string.invalid_hostname), Toast.LENGTH_SHORT).show()
}catch(e:SecurityException){
Toast.makeText(myContext, myContext.getString(R.string.security_exception), Toast.LENGTH_SHORT).show()
}finally {
status = dnsStatus[myDpm.getGlobalPrivateDnsMode(myComponent)]
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.set_dns_host))
}
}
}
@SuppressLint("NewApi")
@Composable
private fun NetLog(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
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))
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)
if(log!=null){
for(i in log){ Log.d("NetLog",i.toString()) }
Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show()
}else{
Log.d("NetLog",myContext.getString(R.string.none))
Toast.makeText(myContext, myContext.getString(R.string.none),Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.retrieve))
}
}
}
@SuppressLint("NewApi")
@Composable
private fun WifiKeypair(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val focusMgr = LocalFocusManager.current
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
var keyPair by remember{mutableStateOf("")}
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()
)
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 = {
val result = myDpm.grantKeyPairToWifiAuth(keyPair)
Toast.makeText(myContext, myContext.getString(if(result){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.add))
}
Button(
onClick = {
val result = myDpm.revokeKeyPairFromWifiAuth(keyPair)
Toast.makeText(myContext, myContext.getString(if(result){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.remove))
}
}
}
}
@SuppressLint("NewApi")
@Composable
private fun APN(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
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()
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))
TextField(
value = inputNum,
label = { Text("APN")},
onValueChange = {inputNum = it},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp),
enabled = !nextStep
)
}else{
Text(text = stringResource(R.string.no_apn_you_should_create_one))
}
Button(
onClick = {focusMgr.clearFocus(); nextStep=!nextStep},
modifier = Modifier.fillMaxWidth(),
enabled = inputNum!=""&&(nextStep||inputNum=="0"||setting[inputNum.toInt()-1]!=null)
) {
Text(stringResource(if(nextStep){R.string.previous_step}else{R.string.next_step}))
}
var result = Builder().build()
AnimatedVisibility(nextStep) {
var carrierEnabled by remember{mutableStateOf(false)}
var inputApnName by remember{mutableStateOf("")}
var user by remember{mutableStateOf("")}
var profileId by remember{mutableStateOf("")}
var selectedAuthType by remember{mutableIntStateOf(AUTH_TYPE_NONE)}
var carrierId by remember{mutableStateOf("$UNKNOWN_CARRIER_ID")}
var apnTypeBitmask by remember{mutableStateOf("")}
var entryName by remember{mutableStateOf("")}
var mmsProxyAddress by remember{mutableStateOf("")}
var mmsProxyPort by remember{mutableStateOf("")}
var proxyAddress by remember{mutableStateOf("")}
var proxyPort by remember{mutableStateOf("")}
var mmsc by remember{mutableStateOf("")}
var mtuV4 by remember{mutableStateOf("")}
var mtuV6 by remember{mutableStateOf("")}
var mvnoType by remember{mutableIntStateOf(-1)}
var networkTypeBitmask by remember{mutableStateOf("")}
var operatorNumeric by remember{mutableStateOf("")}
var password by remember{mutableStateOf("")}
var persistent by remember{mutableStateOf(false)}
var protocol by remember{mutableIntStateOf(-1)}
var roamingProtocol by remember{mutableIntStateOf(-1)}
var id by remember{mutableIntStateOf(0)}
if(inputNum!="0"){
val current = setting[inputNum.toInt()-1]
id = current.id
carrierEnabled = current.isEnabled
inputApnName = current.apnName
user = current.user
if(VERSION.SDK_INT>=33){profileId = current.profileId.toString()}
selectedAuthType = current.authType
apnTypeBitmask = current.apnTypeBitmask.toString()
entryName = current.entryName
if(VERSION.SDK_INT>=29){mmsProxyAddress = current.mmsProxyAddressAsString}
mmsProxyPort = current.mmsProxyPort.toString()
if(VERSION.SDK_INT>=29){proxyAddress = current.proxyAddressAsString}
proxyPort = current.proxyPort.toString()
mmsc = current.mmsc.toString()
if(VERSION.SDK_INT>=33){ mtuV4 = current.mtuV4.toString(); mtuV6 = current.mtuV6.toString() }
mvnoType = current.mvnoType
networkTypeBitmask = current.networkTypeBitmask.toString()
operatorNumeric = current.operatorNumeric
password = current.password
if(VERSION.SDK_INT>=33){persistent = current.isPersistent}
protocol = current.protocol
roamingProtocol = current.roamingProtocol
}
Column {
Text(text = "APN", style = typography.titleLarge)
TextField(
value = inputApnName,
onValueChange = {inputApnName=it},
label = {Text(stringResource(R.string.name))},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){
Text(text = stringResource(R.string.enable), style = typography.titleLarge)
Switch(checked = carrierEnabled, onCheckedChange = {carrierEnabled=it})
}
Text(text = stringResource(R.string.user_name), style = typography.titleLarge)
TextField(
value = user,
onValueChange = {user=it},
label = {Text(stringResource(R.string.user_name))},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
if(VERSION.SDK_INT>=33){
Text(text = stringResource(R.string.profile_id), style = typography.titleLarge)
TextField(
value = profileId,
onValueChange = {profileId=it},
label = {Text("ID")},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
}
Text(text = stringResource(R.string.auth_type), style = typography.titleLarge)
RadioButtonItem("",{selectedAuthType==AUTH_TYPE_NONE},{selectedAuthType=AUTH_TYPE_NONE})
RadioButtonItem("CHAP",{selectedAuthType==AUTH_TYPE_CHAP},{selectedAuthType=AUTH_TYPE_CHAP})
RadioButtonItem("PAP",{selectedAuthType==AUTH_TYPE_PAP},{selectedAuthType=AUTH_TYPE_PAP})
RadioButtonItem("PAP/CHAP",{selectedAuthType==AUTH_TYPE_PAP_OR_CHAP},{selectedAuthType=AUTH_TYPE_PAP_OR_CHAP})
if(VERSION.SDK_INT>=29){
val ts = myContext.getSystemService(ComponentActivity.TELEPHONY_SERVICE) as TelephonyManager
carrierId = ts.simCarrierId.toString()
Text(text = "CarrierID", style = typography.titleLarge)
TextField(
value = carrierId,
onValueChange = {carrierId=it},
label = {Text("ID")},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
}
Text(text = stringResource(R.string.apn_type), style = typography.titleLarge)
TextField(
value = apnTypeBitmask,
onValueChange = {apnTypeBitmask=it},
label = {Text(stringResource(R.string.bitmask))},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
Text(text = stringResource(R.string.description), style = typography.titleLarge)
TextField(
value = entryName,
onValueChange = {entryName=it},
label = {Text(stringResource(R.string.description))},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
Text(text = stringResource(R.string.mms_proxy), style = typography.titleLarge)
if(VERSION.SDK_INT>=29){
TextField(
value = mmsProxyAddress,
onValueChange = {mmsProxyAddress=it},
label = {Text(stringResource(R.string.address))},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
}
TextField(
value = mmsProxyPort,
onValueChange = {mmsProxyPort=it},
label = {Text(stringResource(R.string.port))},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
Text(text = stringResource(R.string.proxy), style = typography.titleLarge)
if(VERSION.SDK_INT>=29){
TextField(
value = proxyAddress,
onValueChange = {proxyAddress=it},
label = {Text(stringResource(R.string.address))},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
}
TextField(
value = proxyPort,
onValueChange = {proxyPort=it},
label = {Text(stringResource(R.string.port))},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
Text(text = "MMSC", style = typography.titleLarge)
TextField(
value = mmsc,
onValueChange = {mmsc=it},
label = {Text("Uri")},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
if(VERSION.SDK_INT>=33){
Text(text = "MTU", style = typography.titleLarge)
TextField(
value = mtuV4,
onValueChange = {mtuV4=it},
label = {Text("IPV4")},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
TextField(
value = mtuV6,
onValueChange = {mtuV6=it},
label = {Text("IPV6")},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
}
Text(text = "MVNO", style = typography.titleLarge)
RadioButtonItem("SPN",{mvnoType==MVNO_TYPE_SPN},{mvnoType=MVNO_TYPE_SPN})
RadioButtonItem("IMSI",{mvnoType==MVNO_TYPE_IMSI},{mvnoType=MVNO_TYPE_IMSI})
RadioButtonItem("GID",{mvnoType==MVNO_TYPE_GID},{mvnoType=MVNO_TYPE_GID})
RadioButtonItem("ICCID",{mvnoType==MVNO_TYPE_ICCID},{mvnoType=MVNO_TYPE_ICCID})
Text(text = stringResource(R.string.network_type), style = typography.titleLarge)
TextField(
value = networkTypeBitmask,
onValueChange = {networkTypeBitmask=it},
label = {Text(stringResource(R.string.bitmask))},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
Text(text = "OperatorNumeric", style = typography.titleLarge)
TextField(
value = operatorNumeric,
onValueChange = {operatorNumeric=it},
label = {Text("ID")},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
Text(text = stringResource(R.string.password), style = typography.titleLarge)
TextField(
value = password,
onValueChange = {password=it},
label = {Text(stringResource(R.string.password))},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(top = 2.dp, bottom = 4.dp)
)
if(VERSION.SDK_INT>=33){
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically){
Text(text = stringResource(R.string.persistent), style = typography.titleLarge)
Switch(checked = persistent, onCheckedChange = {persistent=it})
}
}
Text(text = stringResource(R.string.protocol), style = typography.titleLarge)
RadioButtonItem("IPV4",{protocol==PROTOCOL_IP},{protocol=PROTOCOL_IP})
RadioButtonItem("IPV6",{protocol==PROTOCOL_IPV6},{protocol=PROTOCOL_IPV6})
RadioButtonItem("IPV4/IPV6",{protocol==PROTOCOL_IPV4V6},{protocol=PROTOCOL_IPV4V6})
RadioButtonItem("PPP",{protocol==PROTOCOL_PPP},{protocol=PROTOCOL_PPP})
if(VERSION.SDK_INT>=29){
RadioButtonItem("non-IP",{protocol==PROTOCOL_NON_IP},{protocol=PROTOCOL_NON_IP})
RadioButtonItem("Unstructured",{protocol==PROTOCOL_UNSTRUCTURED},{protocol=PROTOCOL_UNSTRUCTURED})
}
Text(text = stringResource(R.string.roaming_protocol), style = typography.titleLarge)
RadioButtonItem("IPV4",{roamingProtocol==PROTOCOL_IP},{roamingProtocol=PROTOCOL_IP})
RadioButtonItem("IPV6",{roamingProtocol==PROTOCOL_IPV6},{roamingProtocol=PROTOCOL_IPV6})
RadioButtonItem("IPV4/IPV6",{roamingProtocol==PROTOCOL_IPV4V6},{roamingProtocol=PROTOCOL_IPV4V6})
RadioButtonItem("PPP",{roamingProtocol==PROTOCOL_PPP},{roamingProtocol=PROTOCOL_PPP})
if(VERSION.SDK_INT>=29){
RadioButtonItem("non-IP",{roamingProtocol==PROTOCOL_NON_IP},{roamingProtocol=PROTOCOL_NON_IP})
RadioButtonItem("Unstructured",{roamingProtocol==PROTOCOL_UNSTRUCTURED},{roamingProtocol=PROTOCOL_UNSTRUCTURED})
}
var finalStep by remember{mutableStateOf(false)}
Button(
onClick = {
if(!finalStep){
builder.setCarrierEnabled(carrierEnabled)
builder.setApnName(inputApnName)
builder.setUser(user)
if(VERSION.SDK_INT>=33){builder.setProfileId(profileId.toInt())}
builder.setAuthType(selectedAuthType)
if(VERSION.SDK_INT>=29){builder.setCarrierId(carrierId.toInt())}
builder.setApnTypeBitmask(apnTypeBitmask.toInt())
builder.setEntryName(entryName)
if(VERSION.SDK_INT>=29){builder.setMmsProxyAddress(mmsProxyAddress)}
builder.setMmsProxyPort(mmsProxyPort.toInt())
if(VERSION.SDK_INT>=29){builder.setProxyAddress(proxyAddress)}
builder.setProxyPort(proxyPort.toInt())
builder.setMmsc(mmsc.toUri())
if(VERSION.SDK_INT>=33){ builder.setMtuV4(mtuV4.toInt()); builder.setMtuV6(mtuV6.toInt()) }
builder.setMvnoType(mvnoType)
builder.setNetworkTypeBitmask(networkTypeBitmask.toInt())
builder.setOperatorNumeric(operatorNumeric)
builder.setPassword(password)
if(VERSION.SDK_INT>=33){builder.setPersistent(persistent)}
builder.setProtocol(protocol)
builder.setRoamingProtocol(roamingProtocol)
result = builder.build()
}
finalStep=!finalStep
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(if(finalStep){R.string.previous_step}else{R.string.next_step}))
}
AnimatedVisibility(finalStep) {
if(inputNum=="0"){
Button(
onClick = {myDpm.addOverrideApn(myComponent,result)},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.create))
}
}else{
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){
Button(
onClick = {
val success = myDpm.updateOverrideApn(myComponent,id,result)
Toast.makeText(myContext, myContext.getString(if(success){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show()
},
Modifier.fillMaxWidth(0.49F)
){
Text(stringResource(R.string.update))
}
Button(
onClick = {
val success = myDpm.removeOverrideApn(myComponent,id)
Toast.makeText(myContext, if(success){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show()
},
Modifier.fillMaxWidth(0.96F)
){
Text(stringResource(R.string.remove))
}
}
}
}
}
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}

View File

@@ -0,0 +1,632 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManager.*
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build.VERSION
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
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.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.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.ui.*
import com.bintianqi.owndroid.ui.theme.bgColor
@Composable
fun Password(navCtrl: NavHostController){
val localNavCtrl = rememberNavController()
val backStackEntry by localNavCtrl.currentBackStackEntryAsState()
val scrollState = rememberScrollState()
/*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)
)*/
TopBar(backStackEntry,navCtrl,localNavCtrl){
if(backStackEntry?.destination?.route=="Home"&&scrollState.maxValue>80){
Text(
text = stringResource(R.string.password_and_keyguard),
modifier = Modifier.alpha((maxOf(scrollState.value-30,0)).toFloat()/80)
)
}
}
}
){
NavHost(
navController = localNavCtrl, startDestination = "Home",
enterTransition = Animations().navHostEnterTransition,
exitTransition = Animations().navHostExitTransition,
popEnterTransition = Animations().navHostPopEnterTransition,
popExitTransition = Animations().navHostPopExitTransition,
modifier = Modifier.background(bgColor).padding(top = it.calculateTopPadding())
){
composable(route = "Home"){Home(localNavCtrl,scrollState)}
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
private fun Home(navCtrl:NavHostController,scrollState: ScrollState){
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)){
Text(text = stringResource(R.string.password_and_keyguard), style = typography.headlineLarge, modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp))
SubPageItem(R.string.password_info,"",R.drawable.info_fill0){navCtrl.navigate("PasswordInfo")}
if(VERSION.SDK_INT>=26){
SubPageItem(R.string.reset_password_token,"",R.drawable.key_vertical_fill0){navCtrl.navigate("ResetPasswordToken")}
}
SubPageItem(R.string.reset_password,"",R.drawable.lock_reset_fill0){navCtrl.navigate("ResetPassword")}
if(VERSION.SDK_INT>=31){
SubPageItem(R.string.required_password_complexity,"",R.drawable.password_fill0){navCtrl.navigate("RequirePasswordComplexity")}
}
SubPageItem(R.string.keyguard_disabled_features,"",R.drawable.screen_lock_portrait_fill0){navCtrl.navigate("KeyguardDisabledFeatures")}
SubPageItem(R.string.max_time_to_lock,"",R.drawable.schedule_fill0){navCtrl.navigate("MaxTimeToLock")}
SubPageItem(R.string.pwd_timeout,"",R.drawable.lock_clock_fill0){navCtrl.navigate("PasswordTimeout")}
SubPageItem(R.string.max_pwd_fail,"",R.drawable.no_encryption_fill0){navCtrl.navigate("MaxPasswordFail")}
SubPageItem(R.string.pwd_history,"",R.drawable.history_fill0){navCtrl.navigate("PasswordHistoryLength")}
SubPageItem(R.string.required_password_quality,"",R.drawable.password_fill0){navCtrl.navigate("RequirePasswordQuality")}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
private fun PasswordInfo(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext, Receiver::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
private fun ResetPasswordToken(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::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(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(),
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()
) {
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
private fun ResetPassword(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::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)) }
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},
enabled = !confirmed&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm)||myDpm.isAdminActive(myComponent)),
label = { Text(stringResource(R.string.password))},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth()
)
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),
{resetPwdFlag==RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}, {resetPwdFlag=RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}
)
}
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
}else{ Toast.makeText(myContext, myContext.getString(R.string.require_4_digit_password), Toast.LENGTH_SHORT).show() }
},
enabled = isDeviceOwner(myDpm) || isProfileOwner(myDpm) || myDpm.isAdminActive(myComponent),
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = if(confirmed){ colorScheme.primary }else{ colorScheme.error },
contentColor = if(confirmed){ colorScheme.onPrimary }else{ colorScheme.onError }
)
) {
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 = {
val resetSuccess = myDpm.resetPasswordWithToken(myComponent,newPwd,myByteArray,resetPwdFlag)
if(resetSuccess){ Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show();newPwd=""}
else{ Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() }
confirmed=false
},
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError),
enabled = confirmed&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm)),
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.reset_password_with_token))
}
}
Button(
onClick = {
val resetSuccess = myDpm.resetPassword(newPwd,resetPwdFlag)
if(resetSuccess){ Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show(); newPwd=""}
else{ Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show() }
confirmed=false
},
enabled = confirmed,
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError),
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.reset_password_deprecated))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@SuppressLint("NewApi")
@Composable
private fun PasswordComplexity(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
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})
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 = 30.dp))
}
}
@Composable
private fun ScreenTimeout(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::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
private fun MaxFailedPasswordForWipe(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
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))
}
}
}
@Composable
private fun PasswordExpiration(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::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))
}
}
}
@Composable
private fun PasswordHistoryLength(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::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,Receiver::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})
AnimatedVisibility(state==2) {
Column {
CheckBoxItem(stringResource(R.string.keyguard_disabled_features_widgets),{widgets},{widgets=!widgets})
CheckBoxItem(stringResource(R.string.keyguard_disabled_features_camera),{camera},{camera=!camera})
CheckBoxItem(stringResource(R.string.keyguard_disabled_features_notification),{notification},{notification=!notification})
CheckBoxItem(stringResource(R.string.keyguard_disabled_features_unredacted_notification),{unredacted},{unredacted=!unredacted})
CheckBoxItem(stringResource(R.string.keyguard_disabled_features_trust_agents),{agents},{agents=!agents})
CheckBoxItem(stringResource(R.string.keyguard_disabled_features_fingerprint),{fingerprint},{fingerprint=!fingerprint})
if(VERSION.SDK_INT>=24){ CheckBoxItem(stringResource(R.string.keyguard_disabled_features_remote_input),{remote}, {remote=!remote}) }
if(VERSION.SDK_INT>=28){
CheckBoxItem(stringResource(R.string.keyguard_disabled_features_face),{face},{face=!face})
CheckBoxItem(stringResource(R.string.keyguard_disabled_features_iris),{iris},{iris=!iris})
CheckBoxItem(stringResource(R.string.keyguard_disabled_features_biometrics),{biometrics},{biometrics=!biometrics})
}
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
if(state==0){ result = 0 }
else if(state==1){ result = KEYGUARD_DISABLE_FEATURES_ALL }
else{
if(shortcuts&&VERSION.SDK_INT>=34){result+=KEYGUARD_DISABLE_SHORTCUTS_ALL}
if(biometrics&&VERSION.SDK_INT>=28){result+=KEYGUARD_DISABLE_BIOMETRICS}
if(iris&&VERSION.SDK_INT>=28){result+=KEYGUARD_DISABLE_IRIS}
if(face&&VERSION.SDK_INT>=28){result+=KEYGUARD_DISABLE_FACE}
if(remote&&VERSION.SDK_INT>=24){result+=KEYGUARD_DISABLE_REMOTE_INPUT}
if(fingerprint){result+=KEYGUARD_DISABLE_FINGERPRINT}
if(agents){result+=KEYGUARD_DISABLE_TRUST_AGENTS}
if(unredacted){result+=KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS}
if(notification){result+=KEYGUARD_DISABLE_SECURE_NOTIFICATIONS}
if(camera){result+=KEYGUARD_DISABLE_SECURE_CAMERA}
if(widgets){result+=KEYGUARD_DISABLE_WIDGETS_ALL}
}
myDpm.setKeyguardDisabledFeatures(myComponent,result)
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
calculateCustomFeature()
},
enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm),
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.apply))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
private fun PasswordQuality(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
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})
RadioButtonItem(passwordQuality[3].second,{selectedItem==passwordQuality[3].first},{selectedItem=passwordQuality[3].first})
RadioButtonItem(passwordQuality[4].second,{selectedItem==passwordQuality[4].first},{selectedItem=passwordQuality[4].first})
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})
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
myDpm.setPasswordQuality(myComponent,selectedItem)
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))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
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)
if (confirmIntent != null) {
startActivity(myContext,confirmIntent, null)
} else {
Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show()
}
}

View File

@@ -0,0 +1,545 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build.VERSION
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.compose.foundation.*
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.material3.*
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
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.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.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.ui.*
import com.bintianqi.owndroid.ui.theme.bgColor
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
fun DpmPermissions(navCtrl:NavHostController){
val localNavCtrl = rememberNavController()
val backStackEntry by localNavCtrl.currentBackStackEntryAsState()
val scrollState = rememberScrollState()
/*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)
)*/
TopBar(backStackEntry,navCtrl,localNavCtrl){
if(backStackEntry?.destination?.route=="Home"&&scrollState.maxValue>80){
Text(
text = stringResource(R.string.permission),
modifier = Modifier.alpha((maxOf(scrollState.value-30,0)).toFloat()/80)
)
}
}
}
){
NavHost(
navController = localNavCtrl, startDestination = "Home",
enterTransition = Animations().navHostEnterTransition,
exitTransition = Animations().navHostExitTransition,
popEnterTransition = Animations().navHostPopEnterTransition,
popExitTransition = Animations().navHostPopExitTransition,
modifier = Modifier.background(bgColor).padding(top = it.calculateTopPadding())
){
composable(route = "Home"){Home(localNavCtrl,scrollState)}
composable(route = "Shizuku"){ShizukuActivate()}
composable(route = "DeviceAdmin"){DeviceAdmin(navCtrl)}
composable(route = "ProfileOwner"){ProfileOwner()}
composable(route = "DeviceOwner"){DeviceOwner(navCtrl)}
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,listScrollState:ScrollState){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext, Receiver::class.java)
Column(modifier = Modifier.fillMaxSize().verticalScroll(listScrollState)) {
Text(text = stringResource(R.string.permission), style = typography.headlineLarge, modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.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,"",R.drawable.perm_device_information_fill0){localNavCtrl.navigate("DeviceInfo")}
if(VERSION.SDK_INT>=31&&(isProfileOwner(myDpm)|| isDeviceOwner(myDpm))){
SubPageItem(R.string.enrollment_specific_id,"",R.drawable.id_card_fill0){localNavCtrl.navigate("SpecificID")}
}
if((VERSION.SDK_INT>=26&&isDeviceOwner(myDpm))||(VERSION.SDK_INT>=24&&isProfileOwner(myDpm))){
SubPageItem(R.string.org_name,"",R.drawable.corporate_fare_fill0){localNavCtrl.navigate("OrgName")}
}
if(isDeviceOwner(myDpm) || isProfileOwner(myDpm)){
SubPageItem(R.string.account_types_management_disabled,"",R.drawable.account_circle_fill0){localNavCtrl.navigate("NoManagementAccount")}
}
if(VERSION.SDK_INT>=24&&isDeviceOwner(myDpm)){
SubPageItem(R.string.device_owner_lock_screen_info,"",R.drawable.screen_lock_portrait_fill0){localNavCtrl.navigate("LockScreenInfo")}
}
if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){
SubPageItem(R.string.support_msg,"",R.drawable.chat_fill0){localNavCtrl.navigate("SupportMsg")}
}
if(VERSION.SDK_INT>=28&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){
SubPageItem(R.string.transform_ownership,"",R.drawable.admin_panel_settings_fill0){localNavCtrl.navigate("TransformOwnership")}
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@SuppressLint("NewApi")
@Composable
private fun LockScreenInfo(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::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.device_owner_lock_screen_info), style = typography.headlineLarge)
OutlinedTextField(
value = infoText,
label = {Text(stringResource(R.string.device_owner_lock_screen_info))},
onValueChange = { infoText=it },
modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp)
)
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
private fun DeviceAdmin(navCtrl: NavHostController){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val co = rememberCoroutineScope()
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)
co.launch{ delay(600); if(!myDpm.isAdminActive(myComponent)){navCtrl.navigateUp()} }
},
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError)
) {
Text(stringResource(R.string.deactivate))
}
}
} 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))
}
CopyTextButton(myContext, R.string.copy_command, stringResource(R.string.activate_device_admin_command))
}
}
}
@Composable
private fun ProfileOwner(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
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)},
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError)
) {
Text(stringResource(R.string.deactivate))
}
}
if(!isProfileOwner(myDpm)){
SelectionContainer{
Text(text = stringResource(R.string.activate_profile_owner_command))
}
CopyTextButton(myContext, R.string.copy_command, stringResource(R.string.activate_profile_owner_command))
}
}
}
@Composable
private fun DeviceOwner(navCtrl: NavHostController){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val co = rememberCoroutineScope()
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)
co.launch{ delay(600); if(!isDeviceOwner(myDpm)){navCtrl.navigateUp()} }
},
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError)
) {
Text(text = stringResource(R.string.deactivate))
}
}
if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){
SelectionContainer{
Text(text = stringResource(R.string.activate_device_owner_command))
}
CopyTextButton(myContext, R.string.copy_command, 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)
}
}
}
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)
}
}
}
}
@SuppressLint("NewApi")
@Composable
private fun SpecificID(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){
val specificId = myDpm.enrollmentSpecificId
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) }
}else{
Text(stringResource(R.string.require_set_org_id))
}
}
}
@SuppressLint("NewApi")
@Composable
private fun OrgName(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){
var orgName by remember{mutableStateOf(try{myDpm.getOrganizationName(myComponent).toString()}catch(e:SecurityException){""})}
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()
myDpm.setOrganizationName(myComponent,orgName)
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
){
Text(stringResource(R.string.apply))
}
}
}
@SuppressLint("NewApi")
@Composable
private fun SupportMsg(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
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)
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = shortMsg,
label = {Text(stringResource(R.string.short_support_msg))},
onValueChange = { shortMsg=it },
modifier = Modifier.focusable().fillMaxWidth()
)
Spacer(Modifier.padding(vertical = 2.dp))
OutlinedTextField(
value = longMsg,
label = {Text(stringResource(R.string.long_support_msg))},
onValueChange = { longMsg=it },
modifier = Modifier.focusable().fillMaxWidth()
)
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
focusMgr.clearFocus()
myDpm.setShortSupportMessage(myComponent, shortMsg)
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))
}
Spacer(Modifier.padding(vertical = 1.dp))
Button(
onClick = {
focusMgr.clearFocus()
myDpm.setShortSupportMessage(myComponent, null)
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 = 5.dp))
Information{Text(text = stringResource(R.string.support_msg_desc))}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
private fun NoManageAccount(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.account_types_management_disabled), style = typography.headlineLarge)
Text(stringResource(R.string.unknown_effect))
var accountList by remember{ mutableStateOf("") }
val refreshList = {
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 inited by remember{mutableStateOf(false)}
if(!inited){ refreshList(); inited=true }
Spacer(Modifier.padding(vertical = 5.dp))
Text(text = if(accountList==""){stringResource(R.string.none)}else{accountList})
var inputText by remember{ mutableStateOf("") }
OutlinedTextField(
value = inputText,
onValueChange = {inputText=it},
label = {Text(stringResource(R.string.account_types))},
modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 4.dp),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()})
)
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
private fun TransformOwnership(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
val focusRequester = FocusRequester()
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 8.dp)){
var pkg by remember{mutableStateOf("")}
var cls by remember{mutableStateOf("")}
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))
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = pkg, onValueChange = {pkg = it}, label = {Text(stringResource(R.string.target_package_name))},
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = {focusRequester.requestFocus()})
)
Spacer(Modifier.padding(vertical = 2.dp))
OutlinedTextField(
value = cls, onValueChange = {cls = it}, label = {Text(stringResource(R.string.target_class_name))},
modifier = Modifier.focusRequester(focusRequester).fillMaxWidth(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()})
)
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
try {
myDpm.transferOwnership(myComponent,ComponentName(pkg, cls),null)
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()
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.transform))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
private fun activateDeviceAdmin(inputContext:Context,inputComponent:ComponentName){
try {
val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, inputComponent)
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, inputContext.getString(R.string.activate_device_admin_here))
startActivity(inputContext,intent,null)
}catch(e:ActivityNotFoundException){
Toast.makeText(inputContext,inputContext.getString(R.string.unsupported),Toast.LENGTH_SHORT).show()
}
}

View File

@@ -0,0 +1,241 @@
package com.bintianqi.owndroid.dpm
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.os.Binder
import android.os.Build.VERSION
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.SpringSpec
import androidx.compose.foundation.focusable
import androidx.compose.foundation.horizontalScroll
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.material.icons.Icons
import androidx.compose.material.icons.rounded.Warning
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
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.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.apache.commons.io.IOUtils
import java.io.*
@Composable
fun ShizukuActivate(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext, Receiver::class.java)
val focusMgr = LocalFocusManager.current
val filesDir = myContext.filesDir
LaunchedEffect(Unit){ extractRish(myContext) }
val coScope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val outputTextScrollState = rememberScrollState()
Column(
modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(scrollState),
horizontalAlignment = Alignment.CenterHorizontally
){
var outputText by remember{mutableStateOf("")}
if(Binder.getCallingUid()/100000!=0){
Row{
Icon(imageVector = Icons.Rounded.Warning, contentDescription = null, tint = colorScheme.onErrorContainer)
Text(text = stringResource(R.string.not_primary_user_not_support_shizuku), color = colorScheme.onErrorContainer)
}
}
Button(
onClick = {
coScope.launch {
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
outputTextScrollState.animateScrollTo(0, scrollAnim())
val getUid = executeCommand(myContext, "sh rish.sh","id -u",null,filesDir)
outputText = if(getUid.contains("2000")){
myContext.getString(R.string.shizuku_activated_shell)
}else if(getUid.contains("0")){
myContext.getString(R.string.shizuku_activated_root)
}else if(getUid.contains("Error: 1")){
myContext.getString(R.string.shizuku_not_started)
}else{
getUid
}
}
}
) {
Text(text = stringResource(R.string.check_shizuku))
}
Button(
onClick = {
coScope.launch{
outputText= executeCommand(myContext, "sh rish.sh","dpm list-owners",null,filesDir)
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
outputTextScrollState.animateScrollTo(0, scrollAnim())
}
}
) {
Text(text = stringResource(R.string.list_owners))
}
Spacer(Modifier.padding(vertical = 5.dp))
if(!isDeviceOwner(myDpm)&&!isProfileOwner(myDpm)){
Column {
if(!myDpm.isAdminActive(myComponent)){
Button(
onClick = {
coScope.launch{
outputText = executeCommand(myContext, "sh rish.sh", myContext.getString(R.string.dpm_activate_da_command), null, filesDir)
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
outputTextScrollState.animateScrollTo(0, scrollAnim())
}
}
) {
Text(text = stringResource(R.string.activate_device_admin))
}
}
Button(
onClick = {
coScope.launch{
outputText = executeCommand(myContext, "sh rish.sh", myContext.getString(R.string.dpm_activate_po_command), null, filesDir)
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
outputTextScrollState.animateScrollTo(0, scrollAnim())
}
}
) {
Text(text = stringResource(R.string.activate_profile_owner))
}
Button(
onClick = {
coScope.launch{
outputText = executeCommand(myContext, "sh rish.sh", myContext.getString(R.string.dpm_activate_do_command), null, filesDir)
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
outputTextScrollState.animateScrollTo(0, scrollAnim())
}
}
) {
Text(text = stringResource(R.string.activate_device_owner))
}
}
}
if(
VERSION.SDK_INT>=30&&!isDeviceOwner(myDpm)&&!myDpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)&&
!myDpm.isOrganizationOwnedDeviceWithManagedProfile
){
Column {
Text(text = stringResource(R.string.org_owned_work_profile), style = typography.titleLarge, color = colorScheme.onPrimaryContainer)
Text(text = stringResource(R.string.input_userid_of_work_profile))
var inputUserID by remember{mutableStateOf("")}
OutlinedTextField(
value = inputUserID, onValueChange = {inputUserID=it},
label = {Text("UserID")},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp)
)
Button(
onClick = {
coScope.launch{
focusMgr.clearFocus()
outputText = executeCommand(
myContext, "sh rish.sh", myContext.getString(R.string.activate_org_profile_command_with_user_id, inputUserID),
null, filesDir
)
scrollState.animateScrollTo(scrollState.maxValue, scrollAnim())
outputTextScrollState.animateScrollTo(0, scrollAnim())
if(myDpm.isOrganizationOwnedDeviceWithManagedProfile){
Toast.makeText(myContext, myContext.getString(R.string.success),Toast.LENGTH_SHORT).show()
}
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.activate))
}
}
}
SelectionContainer(modifier = Modifier.align(Alignment.Start).horizontalScroll(outputTextScrollState)){
Text(text = outputText, softWrap = false, modifier = Modifier.padding(4.dp))
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Stable
fun <T> scrollAnim(
dampingRatio: Float = Spring.DampingRatioNoBouncy,
stiffness: Float = Spring.StiffnessMedium,
visibilityThreshold: T? = null
): SpringSpec<T> = SpringSpec(dampingRatio, stiffness, visibilityThreshold)
fun extractRish(myContext:Context){
val assetsMgr = myContext.assets
myContext.deleteFile("rish.sh")
myContext.deleteFile("rish_shizuku.dex")
val shInput = assetsMgr.open("rish.sh")
val shOutput = myContext.openFileOutput("rish.sh",MODE_PRIVATE)
IOUtils.copy(shInput,shOutput)
shOutput.close()
val dexInput = assetsMgr.open("rish_shizuku.dex")
val dexOutput = myContext.openFileOutput("rish_shizuku.dex",MODE_PRIVATE)
IOUtils.copy(dexInput,dexOutput)
dexOutput.close()
if(VERSION.SDK_INT>=34){ Runtime.getRuntime().exec("chmod 400 rish_shizuku.dex",null,myContext.filesDir) }
}
suspend fun executeCommand(myContext: Context, command: String, subCommand:String, env: Array<String>?, dir:File?): String {
var result = ""
val tunnel:ByteArrayInputStream
val process:Process
val outputStream:OutputStream
try {
tunnel = ByteArrayInputStream(subCommand.toByteArray())
process = withContext(Dispatchers.IO){Runtime.getRuntime().exec(command, env, dir)}
outputStream = process.outputStream
IOUtils.copy(tunnel,outputStream)
withContext(Dispatchers.IO){ outputStream.close() }
val exitCode = withContext(Dispatchers.IO){ process.waitFor() }
if(exitCode!=0){ result+="Error: $exitCode" }
}catch(e:Exception){
e.printStackTrace()
return e.toString()
}
try {
val outputReader = BufferedReader(InputStreamReader(process.inputStream))
var outputLine: String
while(withContext(Dispatchers.IO){ outputReader.readLine() }.also {outputLine = it}!=null) { result+="$outputLine\n" }
val errorReader = BufferedReader(InputStreamReader(process.errorStream))
var errorLine: String
while(withContext(Dispatchers.IO){ errorReader.readLine() }.also {errorLine = it}!=null) { result+="$errorLine\n" }
} catch(e: NullPointerException) {
e.printStackTrace()
}
if(result==""){ return myContext.getString(R.string.try_again) }
return result
}

View File

@@ -0,0 +1,914 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
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
import android.os.Binder
import android.os.Build.VERSION
import android.os.UserManager
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.*
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.material3.*
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
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.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.toText
import com.bintianqi.owndroid.ui.*
import com.bintianqi.owndroid.ui.theme.bgColor
import kotlinx.coroutines.delay
import java.util.Date
@Composable
fun SystemManage(navCtrl:NavHostController){
val localNavCtrl = rememberNavController()
val backStackEntry by localNavCtrl.currentBackStackEntryAsState()
val scrollState = rememberScrollState()
/*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)
)*/
TopBar(backStackEntry,navCtrl,localNavCtrl){
if(backStackEntry?.destination?.route=="Home"&&scrollState.maxValue>80){
Text(
text = stringResource(R.string.system_manage),
modifier = Modifier.alpha((maxOf(scrollState.value-30,0)).toFloat()/80)
)
}
}
}
){
NavHost(
navController = localNavCtrl, startDestination = "Home",
enterTransition = Animations().navHostEnterTransition,
exitTransition = Animations().navHostExitTransition,
popEnterTransition = Animations().navHostPopEnterTransition,
popExitTransition = Animations().navHostPopExitTransition,
modifier = Modifier.background(bgColor).padding(top = it.calculateTopPadding())
){
composable(route = "Home"){Home(localNavCtrl,scrollState)}
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,scrollState: ScrollState){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)){
Text(text = stringResource(R.string.system_manage), style = typography.headlineLarge, modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp))
if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){
SubPageItem(R.string.options,"",R.drawable.tune_fill0){navCtrl.navigate("Switches")}
}
SubPageItem(R.string.keyguard,"",R.drawable.screen_lock_portrait_fill0){navCtrl.navigate("Keyguard")}
if(VERSION.SDK_INT>=24){
SubPageItem(R.string.request_bug_report,"",R.drawable.bug_report_fill0){navCtrl.navigate("BugReport")}
SubPageItem(R.string.reboot,"",R.drawable.restart_alt_fill0){navCtrl.navigate("Reboot")}
}
if(VERSION.SDK_INT>=28){
SubPageItem(R.string.edit_time,"",R.drawable.schedule_fill0){navCtrl.navigate("EditTime")}
}
if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){
SubPageItem(R.string.permission_policy,"",R.drawable.key_fill0){navCtrl.navigate("PermissionPolicy")}
}
if(VERSION.SDK_INT>=34&&isDeviceOwner(myDpm)){
SubPageItem(R.string.mte_policy,"",R.drawable.memory_fill0){navCtrl.navigate("MTEPolicy")}
}
if(VERSION.SDK_INT>=31&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){
SubPageItem(R.string.nearby_streaming_policy,"",R.drawable.share_fill0){navCtrl.navigate("NearbyStreamingPolicy")}
}
if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){
SubPageItem(R.string.lock_task_feature,"",R.drawable.lock_fill0){navCtrl.navigate("LockTaskFeatures")}
}
if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){
SubPageItem(R.string.ca_cert,"",R.drawable.license_fill0){navCtrl.navigate("CaCert")}
}
if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||(VERSION.SDK_INT>=30&&isProfileOwner(myDpm)&&myDpm.isOrganizationOwnedDeviceWithManagedProfile))){
SubPageItem(R.string.security_logs,"",R.drawable.description_fill0){navCtrl.navigate("SecurityLogs")}
}
if(VERSION.SDK_INT>=23&&isDeviceOwner(myDpm)){
SubPageItem(R.string.system_update_policy,"",R.drawable.system_update_fill0){navCtrl.navigate("SystemUpdatePolicy")}
}
SubPageItem(R.string.wipe_data,"",R.drawable.warning_fill0){navCtrl.navigate("WipeData")}
Spacer(Modifier.padding(vertical = 30.dp))
LaunchedEffect(Unit){caCert =byteArrayOf()}
}
}
@Composable
private fun Switches(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext, Receiver::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_screenshot, stringResource(R.string.also_disable_aosp_screen_record),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) }
)
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)})
}
}
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
private fun Keyguard(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
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){
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(
onClick = {myDpm.lockNow()},
enabled = myDpm.isAdminActive(myComponent),
modifier = Modifier.fillMaxWidth()
) {
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
private fun BugReport(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::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,Receiver::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,Receiver::class.java)
val focusMgr = LocalFocusManager.current
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))},
onValueChange = {inputTime = it},
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()
)
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
private fun PermissionPolicy(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
var selectedPolicy by remember{mutableIntStateOf(myDpm.getPermissionPolicy(myComponent))}
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)
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.apply))
}
}
}
@SuppressLint("NewApi")
@Composable
private fun MTEPolicy(){
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.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})
RadioButtonItem(stringResource(R.string.disabled), {selectedMtePolicy==MTE_DISABLED}, {selectedMtePolicy=MTE_DISABLED})
Button(
onClick = {
try {
myDpm.mtePolicy = selectedMtePolicy
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
}catch(e:java.lang.UnsupportedOperationException){
Toast.makeText(myContext, myContext.getString(R.string.unsupported), Toast.LENGTH_SHORT).show()
}
selectedMtePolicy = myDpm.mtePolicy
},
modifier = Modifier.fillMaxWidth()
) {
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
private fun NearbyStreamingPolicy(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
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
Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
) {
Text("应用")
}
var notificationPolicy by remember{mutableIntStateOf(myDpm.nearbyNotificationStreamingPolicy)}
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.nearby_notification_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
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))
}
}
@SuppressLint("NewApi")
@Composable
private fun LockTaskFeatures(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
val lockTaskPolicyList = mutableListOf(
LOCK_TASK_FEATURE_NONE,
LOCK_TASK_FEATURE_SYSTEM_INFO,
LOCK_TASK_FEATURE_NOTIFICATIONS,
LOCK_TASK_FEATURE_HOME,
LOCK_TASK_FEATURE_OVERVIEW,
LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
LOCK_TASK_FEATURE_KEYGUARD
)
var sysInfo by remember{mutableStateOf(false)}
var notifications by remember{mutableStateOf(false)}
var home by remember{mutableStateOf(false)}
var overview by remember{mutableStateOf(false)}
var globalAction by remember{mutableStateOf(false)}
var keyGuard by remember{mutableStateOf(false)}
var blockAct by remember{mutableStateOf(false)}
if(VERSION.SDK_INT>=30){lockTaskPolicyList.add(LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK)}
var inited by remember{mutableStateOf(false)}
var custom by remember{mutableStateOf(false)}
val refreshFeature = {
var calculate = myDpm.getLockTaskFeatures(myComponent)
if(calculate!=0){
if(VERSION.SDK_INT>=30&&calculate-lockTaskPolicyList[7]>=0){blockAct=true;calculate-=lockTaskPolicyList[7]}
if(calculate-lockTaskPolicyList[6]>=0){keyGuard=true;calculate-=lockTaskPolicyList[6]}
if(calculate-lockTaskPolicyList[5]>=0){globalAction=true;calculate-=lockTaskPolicyList[5]}
if(calculate-lockTaskPolicyList[4]>=0){overview=true;calculate-=lockTaskPolicyList[4]}
if(calculate-lockTaskPolicyList[3]>=0){home=true;calculate-=lockTaskPolicyList[3]}
if(calculate-lockTaskPolicyList[2]>=0){notifications=true;calculate-=lockTaskPolicyList[2]}
if(calculate-lockTaskPolicyList[1]>=0){sysInfo=true;calculate-=lockTaskPolicyList[1]}
}else{
custom = false
}
}
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})
AnimatedVisibility(custom) {
Column {
CheckBoxItem(stringResource(R.string.ltf_sys_info),{sysInfo},{sysInfo=!sysInfo})
CheckBoxItem(stringResource(R.string.ltf_notifications),{notifications},{notifications=!notifications})
CheckBoxItem(stringResource(R.string.ltf_home),{home},{home=!home})
CheckBoxItem(stringResource(R.string.ltf_overview),{overview},{overview=!overview})
CheckBoxItem(stringResource(R.string.ltf_global_actions),{globalAction},{globalAction=!globalAction})
CheckBoxItem(stringResource(R.string.ltf_keyguard),{keyGuard},{keyGuard=!keyGuard})
if(VERSION.SDK_INT>=30){ CheckBoxItem(stringResource(R.string.ltf_block_activity_start_in_task),{blockAct},{blockAct=!blockAct}) }
}
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
var result = lockTaskPolicyList[0]
if(custom){
if(blockAct&&VERSION.SDK_INT>=30){result+=lockTaskPolicyList[7]}
if(keyGuard){result+=lockTaskPolicyList[6]}
if(globalAction){result+=lockTaskPolicyList[5]}
if(overview){result+=lockTaskPolicyList[4]}
if(home){result+=lockTaskPolicyList[3]}
if(notifications){result+=lockTaskPolicyList[2]}
if(sysInfo){result+=lockTaskPolicyList[1]}
}
myDpm.setLockTaskFeatures(myComponent,result)
refreshFeature()
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
}
) {
Text(stringResource(R.string.apply))
}
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=""
listText = whitelist.toText()
}
LaunchedEffect(Unit){refreshWhitelist()}
Text(text = stringResource(R.string.whitelist_app), style = typography.titleLarge)
SelectionContainer(modifier = Modifier.animateContentSize(Animations().animateListSize)){
Text(text = if(listText==""){ stringResource(R.string.none) }else{listText})
}
OutlinedTextField(
value = inputPkg,
onValueChange = {inputPkg=it},
label = {Text(stringResource(R.string.package_name))},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp)
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){
Button(
onClick = {
focusMgr.clearFocus()
whitelist.add(inputPkg)
myDpm.setLockTaskPackages(myComponent,whitelist.toTypedArray())
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
refreshWhitelist()
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.add))
}
Button(
onClick = {
focusMgr.clearFocus()
if(inputPkg in whitelist){
whitelist.remove(inputPkg)
myDpm.setLockTaskPackages(myComponent,whitelist.toTypedArray())
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show()
}
refreshWhitelist()
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.remove))
}
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
private fun CaCert(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
var exist 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){ 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)
caCertIntent.setType("*/*")
caCertIntent.addCategory(Intent.CATEGORY_OPENABLE)
getCaCert.launch(caCertIntent)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.select_ca_cert))
}
AnimatedVisibility(!isEmpty) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){
Button(
onClick = {
val result = myDpm.installCaCert(myComponent, caCert)
Toast.makeText(myContext, myContext.getString(if(result){R.string.success}else{R.string.fail}), Toast.LENGTH_SHORT).show()
refresh()
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.install))
}
Button(
onClick = {
if(exist){
myDpm.uninstallCaCert(myComponent, caCert)
exist = myDpm.hasCaCertInstalled(myComponent, caCert)
Toast.makeText(myContext, myContext.getString(if(exist){R.string.fail}else{R.string.success}), Toast.LENGTH_SHORT).show()
}else{ Toast.makeText(myContext, myContext.getString(R.string.not_exist), Toast.LENGTH_SHORT).show() }
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.uninstall))
}
}
}
Button(
onClick = {
myDpm.uninstallAllUserCaCerts(myComponent)
Toast.makeText(myContext, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
){
Text(stringResource(R.string.uninstall_all_user_ca_cert))
}
}
}
@SuppressLint("NewApi")
@Composable
private fun SecurityLogs(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())){
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.security_logs), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
Text(text = stringResource(R.string.developing))
SwitchItem(R.string.enable,"",null,{myDpm.isSecurityLoggingEnabled(myComponent)},{myDpm.setSecurityLoggingEnabled(myComponent,it)})
Button(
onClick = {
val log = myDpm.retrieveSecurityLogs(myComponent)
if(log!=null){
for(i in log){ Log.d("SecureLog",i.toString()) }
Toast.makeText(myContext,myContext.getString(R.string.success),Toast.LENGTH_SHORT).show()
}else{
Log.d("SecureLog",myContext.getString(R.string.none))
Toast.makeText(myContext, myContext.getString(R.string.no_logs),Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.security_logs))
}
Button(
onClick = {
val log = myDpm.retrievePreRebootSecurityLogs(myComponent)
if(log!=null){
for(i in log){ Log.d("SecureLog",i.toString()) }
Toast.makeText(myContext,myContext.getString(R.string.success),Toast.LENGTH_SHORT).show()
}else{
Log.d("SecureLog",myContext.getString(R.string.none))
Toast.makeText(myContext,myContext.getString(R.string.no_logs),Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.pre_reboot_security_logs))
}
}
}
@Composable
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,Receiver::class.java)
val focusMgr = LocalFocusManager.current
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)}
var protectionData by remember{mutableStateOf(false)}
var euicc by remember{mutableStateOf(false)}
var silent by remember{mutableStateOf(false)}
var reason by remember{mutableStateOf("")}
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})
}
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},
label = {Text(stringResource(R.string.reason))},
enabled = !confirmed,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 3.dp)
)
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
focusMgr.clearFocus()
flag = 0
if(externalStorage){flag += WIPE_EXTERNAL_STORAGE}
if(protectionData&&VERSION.SDK_INT>=22){flag += WIPE_RESET_PROTECTION_DATA}
if(euicc&&VERSION.SDK_INT>=28){flag += WIPE_EUICC}
if(reason==""){silent = true}
if(silent&&VERSION.SDK_INT>=29){flag += WIPE_SILENTLY}
confirmed=!confirmed
},
colors = ButtonDefaults.buttonColors(
containerColor = if(confirmed){ colorScheme.primary }else{ colorScheme.error },
contentColor = if(confirmed){ colorScheme.onPrimary }else{ colorScheme.onError }
),
enabled = myDpm.isAdminActive(myComponent),
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(if(confirmed){ R.string.cancel }else{ R.string.confirm }))
}
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 = {myDpm.wipeDevice(flag)},
colors = ButtonDefaults.buttonColors(containerColor = colorScheme.error, contentColor = colorScheme.onError),
enabled = confirmed,
modifier = Modifier.fillMaxWidth()
) {
Text("WipeDevice")
}
}
Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT>=24&&isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)){
Information{Text(text = stringResource(R.string.will_delete_work_profile))}
}
if(VERSION.SDK_INT>=34&&Binder.getCallingUid()/100000==0){
Information{Text(text = stringResource(R.string.api34_or_above_wipedata_cannot_in_system_user))}
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
private fun SysUpdatePolicy(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
val sharedPref = myContext.getSharedPreferences("data", Context.MODE_PRIVATE)
val isWear = sharedPref.getBoolean("isWear",false)
val bodyTextStyle = if(isWear){ typography.bodyMedium}else{typography.bodyLarge}
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 {
if(sysUpdateInfo!=null){
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"
else->"false"
}
Text(text = stringResource(R.string.is_security_patch, securityStateDesc), style = bodyTextStyle)
}else{
Text(text = stringResource(R.string.no_system_update), style = bodyTextStyle)
}
}
}
Spacer(Modifier.padding(vertical = 30.dp))
/*if(VERSION.SDK_INT>=29){
Column(modifier = sections()){
var resultUri by remember{mutableStateOf(otaUri)}
Text(text = "安装系统更新", style = typography.titleLarge)
Button(
onClick = {
val getUri = Intent(Intent.ACTION_GET_CONTENT)
getUri.setType("application/zip")
getUri.addCategory(Intent.CATEGORY_OPENABLE)
getOtaPackage.launch(getUri)
},
modifier = Modifier.fillMaxWidth(),
enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm)
) {
Text("选择OTA包")
}
Button(
onClick = {resultUri = otaUri},
modifier = Modifier.fillMaxWidth(),
enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm)
) {
Text("查看OTA包详情")
}
Text("URI: $resultUri")
if(installOta){
Button(
onClick = {
val sysUpdateExecutor = Executors.newCachedThreadPool()
val sysUpdateCallback:InstallSystemUpdateCallback = InstallSystemUpdateCallback
myDpm.installSystemUpdate(myComponent,resultUri,sysUpdateExecutor,sysUpdateCallback)
},
enabled = isDeviceOwner(myDpm)||isProfileOwner(myDpm)
){
Text("安装")
}
}
}
}*/
}
}

View File

@@ -0,0 +1,547 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
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.animation.AnimatedVisibility
import androidx.compose.foundation.*
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.material3.Button
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
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 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.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.toText
import com.bintianqi.owndroid.ui.*
import com.bintianqi.owndroid.ui.theme.bgColor
import com.bintianqi.owndroid.uriToStream
import kotlinx.coroutines.delay
var affiliationID = mutableSetOf<String>()
@Composable
fun UserManage(navCtrl:NavHostController) {
val localNavCtrl = rememberNavController()
val backStackEntry by localNavCtrl.currentBackStackEntryAsState()
val scrollState = rememberScrollState()
/*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)
)*/
TopBar(backStackEntry,navCtrl,localNavCtrl){
if(backStackEntry?.destination?.route=="Home"&&scrollState.maxValue>80){
Text(
text = stringResource(R.string.user_manager),
modifier = Modifier.alpha((maxOf(scrollState.value-30,0)).toFloat()/80)
)
}
}
}
){
NavHost(
navController = localNavCtrl, startDestination = "Home",
enterTransition = Animations().navHostEnterTransition,
exitTransition = Animations().navHostExitTransition,
popEnterTransition = Animations().navHostPopEnterTransition,
popExitTransition = Animations().navHostPopExitTransition,
modifier = Modifier.background(bgColor).padding(top = it.calculateTopPadding())
){
composable(route = "Home"){Home(localNavCtrl,scrollState)}
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()}
}
}
}
@Composable
private fun Home(navCtrl: NavHostController,scrollState: ScrollState){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)){
Text(text = stringResource(R.string.user_manager), style = typography.headlineLarge, modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 15.dp))
SubPageItem(R.string.user_info,"",R.drawable.person_fill0){navCtrl.navigate("UserInfo")}
SubPageItem(R.string.user_operation,"",R.drawable.sync_alt_fill0){navCtrl.navigate("UserOperation")}
if(VERSION.SDK_INT>=24&&isDeviceOwner(myDpm)){
SubPageItem(R.string.create_user,"",R.drawable.person_add_fill0){navCtrl.navigate("CreateUser")}
}
SubPageItem(R.string.edit_username,"",R.drawable.edit_fill0){navCtrl.navigate("EditUsername")}
if(VERSION.SDK_INT>=23&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){
SubPageItem(R.string.change_user_icon,"",R.drawable.account_circle_fill0){navCtrl.navigate("ChangeUserIcon")}
}
if(VERSION.SDK_INT>=28&&isDeviceOwner(myDpm)){
SubPageItem(R.string.user_session_msg,"",R.drawable.notifications_fill0){navCtrl.navigate("UserSessionMessage")}
}
if(VERSION.SDK_INT>=26&&(isDeviceOwner(myDpm)||isProfileOwner(myDpm))){
SubPageItem(R.string.affiliation_id,"",R.drawable.id_card_fill0){navCtrl.navigate("AffiliationID")}
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
private fun CurrentUserInfo(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext, Receiver::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
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,Receiver::class.java)
val focusMgr = LocalFocusManager.current
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(Process.myUserHandle()) }
var useUid by remember{ mutableStateOf(false) }
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = idInput,
onValueChange = {
idInput=it
if(useUid){
if(idInput!=""&&VERSION.SDK_INT>=24){
userHandleById = UserHandle.getUserHandleForUid(idInput.toInt())
}
}else{
val userHandleBySerial = userManager.getUserForSerialNumber(idInput.toLong())
userHandleById = userHandleBySerial ?: Process.myUserHandle()
}
},
label = {Text(if(useUid){"UID"}else{ stringResource(R.string.serial_number) })},
enabled = isDeviceOwner(myDpm),
modifier = Modifier.focusable().fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()})
)
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,myContext), Toast.LENGTH_SHORT).show()
},
enabled = isProfileOwner(myDpm),
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.logout_current_user))
}
}
}
if(VERSION.SDK_INT>=28){
Button(
onClick = {
focusMgr.clearFocus()
val result = myDpm.startUserInBackground(myComponent,userHandleById)
Toast.makeText(myContext, userOperationResultCode(result,myContext), Toast.LENGTH_SHORT).show()
},
enabled = isDeviceOwner(myDpm),
modifier = Modifier.fillMaxWidth()
){
Text(stringResource(R.string.start_in_background))
}
}
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{
val result = myDpm.stopUser(myComponent,userHandleById)
Toast.makeText(myContext, userOperationResultCode(result,myContext), Toast.LENGTH_SHORT).show()
}catch(e:IllegalArgumentException){
Toast.makeText(myContext, myContext.getString(R.string.fail), Toast.LENGTH_SHORT).show()
}
},
enabled = isDeviceOwner(myDpm),
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.user_operation_stop))
}
}
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
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,Receiver::class.java)
val focusMgr = LocalFocusManager.current
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))
OutlinedTextField(
value = userName,
onValueChange = {userName=it},
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(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(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)
focusMgr.clearFocus()
Toast.makeText(myContext, if(newUserHandle!=null){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show()
},
enabled = isDeviceOwner(myDpm),
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.create))
}
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
private fun AffiliationID(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
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))
}
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = input,
onValueChange = {input = it},
label = {Text("ID")},
modifier = Modifier.focusable().fillMaxWidth().padding(vertical = 2.dp),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()})
)
Spacer(Modifier.padding(vertical = 5.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){
Button(
onClick = { affiliationID.add(input); list = affiliationID.toText() },
modifier = Modifier.fillMaxWidth(0.49F)
){
Text(stringResource(R.string.add))
}
Button(
onClick = { affiliationID.remove(input); list = affiliationID.toText() },
modifier = Modifier.fillMaxWidth(0.96F)
){
Text(stringResource(R.string.remove))
}
}
Button(
onClick = {
if("" in affiliationID) {
Toast.makeText(myContext, myContext.getString(R.string.include_empty_string), Toast.LENGTH_SHORT).show()
}else if(affiliationID.isEmpty()){
Toast.makeText(myContext, myContext.getString(R.string.cannot_be_empty), Toast.LENGTH_SHORT).show()
}else{
myDpm.setAffiliationIds(myComponent, affiliationID)
affiliationID = myDpm.getAffiliationIds(myComponent)
list = affiliationID.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
private fun Username(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::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))
OutlinedTextField(
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))
}
}
}
@SuppressLint("NewApi")
@Composable
private fun UserSessionMessage(){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val focusMgr = LocalFocusManager.current
val getStart = myDpm.getStartUserSessionMessage(myComponent)?:""
val getEnd = myDpm.getEndUserSessionMessage(myComponent)?:""
var start by remember{mutableStateOf(getStart.toString())}
var end by remember{mutableStateOf(getEnd.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))
OutlinedTextField(
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 = 2.dp))
OutlinedTextField(
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,Receiver::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_square_image))
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})
if(getContent){intent.addCategory(Intent.CATEGORY_OPENABLE)}
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
getUserIcon.launch(intent)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.select_picture))
}
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, myContext.getString(R.string.success), Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.apply))
}
}
}
}
private fun userOperationResultCode(result:Int, myContext: Context): String {
return when(result){
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

@@ -0,0 +1,319 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.os.Build.VERSION
import android.os.UserManager
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
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.verticalScroll
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
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.bintianqi.owndroid.R
import com.bintianqi.owndroid.Receiver
import com.bintianqi.owndroid.ui.Animations
import com.bintianqi.owndroid.ui.SubPageItem
import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.ui.TopBar
import com.bintianqi.owndroid.ui.theme.bgColor
private data class Restriction(
val restriction:String,
@StringRes val name:Int,
val desc:String,
@DrawableRes val ico:Int
)
@Composable
fun UserRestriction(navCtrl: NavHostController){
val localNavCtrl = rememberNavController()
val backStackEntry by localNavCtrl.currentBackStackEntryAsState()
val scrollState = rememberScrollState()
/*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)
)*/
TopBar(backStackEntry,navCtrl,localNavCtrl){
if(backStackEntry?.destination?.route=="Home"&&scrollState.maxValue>80){
Text(
text = stringResource(R.string.user_restrict),
modifier = Modifier.alpha((maxOf(scrollState.value-30,0)).toFloat()/80)
)
}
}
}
){
NavHost(
navController = localNavCtrl, startDestination = "Home",
enterTransition = Animations().navHostEnterTransition,
exitTransition = Animations().navHostExitTransition,
popEnterTransition = Animations().navHostPopEnterTransition,
popExitTransition = Animations().navHostPopExitTransition,
modifier = Modifier.background(bgColor).padding(top = it.calculateTopPadding())
){
composable(route = "Internet"){Internet()}
composable(route = "Home"){Home(localNavCtrl,scrollState)}
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,scrollState: ScrollState){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext, Receiver::class.java)
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)){
Text(text = stringResource(R.string.user_restrict), style = typography.headlineLarge, modifier = Modifier.padding(top = 8.dp, bottom = 7.dp, start = 15.dp))
Text(text = stringResource(R.string.switch_to_disable_feature), modifier = Modifier.padding(start = 15.dp))
if(isProfileOwner(myDpm)){ Text(text = stringResource(R.string.profile_owner_is_restricted), modifier = Modifier.padding(start = 15.dp)) }
if(isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)))){
Text(text = stringResource(R.string.some_features_invalid_in_work_profile), modifier = Modifier.padding(start = 15.dp))
}
Spacer(Modifier.padding(vertical = 2.dp))
SubPageItem(R.string.network_internet,"",R.drawable.wifi_fill0){navCtrl.navigate("Internet")}
SubPageItem(R.string.more_connectivity,"",R.drawable.devices_other_fill0){navCtrl.navigate("Connectivity")}
SubPageItem(R.string.applications,"",R.drawable.apps_fill0){navCtrl.navigate("Applications")}
SubPageItem(R.string.users,"",R.drawable.account_circle_fill0){navCtrl.navigate("Users")}
SubPageItem(R.string.media,"",R.drawable.volume_up_fill0){navCtrl.navigate("Media")}
SubPageItem(R.string.other,"",R.drawable.more_horiz_fill0){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
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,
restrictionDescription:String,
leadIcon:Int
){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
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()
}
}
},
isDeviceOwner(myDpm)||isProfileOwner(myDpm)
)
}
private class RestrictionData{
fun internet():List<Restriction>{
val list:MutableList<Restriction> = mutableListOf()
list += Restriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, R.string.config_mobile_network,"",R.drawable.signal_cellular_alt_fill0)
list += Restriction(UserManager.DISALLOW_CONFIG_WIFI,R.string.config_wifi,"",R.drawable.wifi_fill0)
if(VERSION.SDK_INT>=24){list += Restriction(UserManager.DISALLOW_DATA_ROAMING,R.string.data_roaming,"",R.drawable.network_cell_fill0)}
if(VERSION.SDK_INT>=34){
list += Restriction(UserManager.DISALLOW_CELLULAR_2G,R.string.cellular_2g,"",R.drawable.network_cell_fill0)
list += Restriction(UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,R.string.ultra_wideband_radio,"",R.drawable.wifi_tethering_fill0)
}
if(VERSION.SDK_INT>=33){
list += Restriction(UserManager.DISALLOW_ADD_WIFI_CONFIG,R.string.add_wifi_conf,"",R.drawable.wifi_fill0)
list += Restriction(UserManager.DISALLOW_CHANGE_WIFI_STATE,R.string.change_wifi_state,"",R.drawable.wifi_fill0)
list += Restriction(UserManager.DISALLOW_WIFI_DIRECT,R.string.wifi_direct,"",R.drawable.wifi_tethering_fill0)
list += Restriction(UserManager.DISALLOW_WIFI_TETHERING,R.string.wifi_tethering,"",R.drawable.wifi_tethering_fill0)
list += Restriction(UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,R.string.share_admin_wifi,"",R.drawable.share_fill0)
}
if(VERSION.SDK_INT>=23){ list += Restriction(UserManager.DISALLOW_NETWORK_RESET,R.string.network_reset,"",R.drawable.reset_wrench_fill0) }
list += Restriction(UserManager.DISALLOW_CONFIG_TETHERING,R.string.config_tethering,"",R.drawable.wifi_tethering_fill0)
list += Restriction(UserManager.DISALLOW_CONFIG_VPN,R.string.config_vpn,"",R.drawable.vpn_key_fill0)
if(VERSION.SDK_INT>=29){list += Restriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS,R.string.config_private_dns,"",R.drawable.dns_fill0)}
if(VERSION.SDK_INT>=28){list += Restriction(UserManager.DISALLOW_AIRPLANE_MODE,R.string.airplane_mode,"",R.drawable.airplanemode_active_fill0)}
list += Restriction(UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,R.string.config_cell_broadcasts,"",R.drawable.cell_tower_fill0)
list += Restriction(UserManager.DISALLOW_SMS,R.string.sms,"",R.drawable.sms_fill0)
list += Restriction(UserManager.DISALLOW_OUTGOING_CALLS,R.string.outgoing_calls,"",R.drawable.phone_forwarded_fill0)
return list
}
fun connectivity(myContext:Context):List<Restriction>{
val list:MutableList<Restriction> = mutableListOf()
if(VERSION.SDK_INT>=26){
list += Restriction(UserManager.DISALLOW_BLUETOOTH,R.string.bluetooth,"",R.drawable.bluetooth_fill0)
list += Restriction(UserManager.DISALLOW_BLUETOOTH_SHARING,R.string.bt_share,"",R.drawable.bluetooth_searching_fill0)
}
list += Restriction(UserManager.DISALLOW_SHARE_LOCATION,R.string.share_location,"",R.drawable.location_on_fill0)
if(VERSION.SDK_INT>=28){list += Restriction(UserManager.DISALLOW_CONFIG_LOCATION,R.string.config_location,"",R.drawable.location_on_fill0)}
if(VERSION.SDK_INT>=22){list += Restriction(UserManager.DISALLOW_OUTGOING_BEAM,R.string.outgoing_beam,"",R.drawable.nfc_fill0)}
list += Restriction(UserManager.DISALLOW_USB_FILE_TRANSFER,R.string.usb_file_transfer,"",R.drawable.usb_fill0)
list += Restriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,R.string.mount_physical_media, "", R.drawable.sd_card_fill0)
if(VERSION.SDK_INT>=28){list += Restriction(UserManager.DISALLOW_PRINTING,R.string.printing,"",R.drawable.print_fill0)}
return list
}
fun application(myContext: Context):List<Restriction>{
val list:MutableList<Restriction> = mutableListOf()
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_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
}
fun media():List<Restriction>{
val list:MutableList<Restriction> = mutableListOf()
if(VERSION.SDK_INT>=28){
list += Restriction(UserManager.DISALLOW_CONFIG_BRIGHTNESS,R.string.config_brightness,"",R.drawable.brightness_5_fill0)
list += Restriction(UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT,R.string.config_scr_timeout,"",R.drawable.screen_lock_portrait_fill0)
list += Restriction(UserManager.DISALLOW_AMBIENT_DISPLAY,R.string.ambient_display,"",R.drawable.brightness_5_fill0)
}
list += Restriction(UserManager.DISALLOW_ADJUST_VOLUME,R.string.adjust_volume,"",R.drawable.volume_up_fill0)
list += Restriction(UserManager.DISALLOW_UNMUTE_MICROPHONE,R.string.unmute_microphone,"",R.drawable.mic_fill0)
if(VERSION.SDK_INT>=31){
list += Restriction(UserManager.DISALLOW_CAMERA_TOGGLE,R.string.camera_toggle,"",R.drawable.cameraswitch_fill0)
list += Restriction(UserManager.DISALLOW_MICROPHONE_TOGGLE,R.string.microphone_toggle,"",R.drawable.mic_fill0)
}
return list
}
fun user():List<Restriction>{
val list:MutableList<Restriction> = mutableListOf()
list += Restriction(UserManager.DISALLOW_ADD_USER,R.string.add_user,"",R.drawable.account_circle_fill0)
list += Restriction(UserManager.DISALLOW_REMOVE_USER,R.string.remove_user,"",R.drawable.account_circle_fill0)
if(VERSION.SDK_INT>=28){list += Restriction(UserManager.DISALLOW_USER_SWITCH,R.string.switch_user,"",R.drawable.account_circle_fill0)}
if(VERSION.SDK_INT>=24){list += Restriction(UserManager.DISALLOW_SET_USER_ICON,R.string.set_user_icon,"",R.drawable.account_circle_fill0)}
list += Restriction(UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,R.string.cross_profile_copy, "",R.drawable.content_paste_fill0)
if(VERSION.SDK_INT>=28){
list += Restriction(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,R.string.share_into_managed_profile,"",R.drawable.share_fill0)
list += Restriction(UserManager.DISALLOW_UNIFIED_PASSWORD,R.string.unified_pwd,"",R.drawable.work_fill0)
}
return list
}
fun other(myContext: Context):List<Restriction>{
val list:MutableList<Restriction> = mutableListOf()
if(VERSION.SDK_INT>=26){ list += Restriction(UserManager.DISALLOW_AUTOFILL,R.string.autofill, "",R.drawable.password_fill0) }
list += Restriction(UserManager.DISALLOW_CONFIG_CREDENTIALS,R.string.config_credentials,"",R.drawable.android_fill0)
if(VERSION.SDK_INT>=29){
list += Restriction(UserManager.DISALLOW_CONTENT_CAPTURE,R.string.content_capture,"",R.drawable.screenshot_fill0)
list += Restriction(UserManager.DISALLOW_CONTENT_SUGGESTIONS,R.string.content_suggestions,"",R.drawable.sms_fill0)
}
list += Restriction(UserManager.DISALLOW_CREATE_WINDOWS,R.string.create_windows, myContext.getString(R.string.create_windows_desc),R.drawable.web_asset)
if(VERSION.SDK_INT>=24){list += Restriction(UserManager.DISALLOW_SET_WALLPAPER,R.string.set_wallpaper,"",R.drawable.wallpaper_fill0)}
if(VERSION.SDK_INT>=34){ list += Restriction(UserManager.DISALLOW_GRANT_ADMIN,R.string.grant_admin,"",R.drawable.security_fill0) }
if(VERSION.SDK_INT>=23){ list += Restriction(UserManager.DISALLOW_FUN,R.string.`fun`, myContext.getString(R.string.fun_desc),R.drawable.stadia_controller_fill0) }
list += Restriction(UserManager.DISALLOW_MODIFY_ACCOUNTS,R.string.modify_accounts,"",R.drawable.manage_accounts_fill0)
if(VERSION.SDK_INT>=28){
list += Restriction(UserManager.DISALLOW_CONFIG_LOCALE,R.string.config_locale,"",R.drawable.language_fill0)
list += Restriction(UserManager.DISALLOW_CONFIG_DATE_TIME,R.string.config_date_time,"",R.drawable.schedule_fill0)
}
if(VERSION.SDK_INT>=28){list += Restriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,R.string.sys_err_dialog,"",R.drawable.warning_fill0)}
list += Restriction(UserManager.DISALLOW_FACTORY_RESET,R.string.factory_reset,"",R.drawable.android_fill0)
if(VERSION.SDK_INT>=23){ list += Restriction(UserManager.DISALLOW_SAFE_BOOT,R.string.safe_boot,"",R.drawable.security_fill0) }
list += Restriction(UserManager.DISALLOW_DEBUGGING_FEATURES,R.string.debug_features,"",R.drawable.adb_fill0)
return list
}
}