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,215 @@
package com.bintianqi.owndroid
import android.app.Activity
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.os.Build.VERSION
import android.os.Bundle
import android.util.DisplayMetrics
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.dpm.*
import com.bintianqi.owndroid.ui.Animations
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import com.bintianqi.owndroid.ui.theme.SetDarkTheme
import com.bintianqi.owndroid.ui.theme.bgColor
lateinit var displayMetrics: DisplayMetrics
@ExperimentalMaterial3Api
class MainActivity : ComponentActivity() {
private fun registerActivityResult(){
getUserIcon = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { userIconUri = it.data?.data }
getApk = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { apkUri = it.data?.data }
getCaCert = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
uriToStream(applicationContext,it.data?.data){stream->
caCert = stream.readBytes()
if(caCert.size>50000){ Toast.makeText(applicationContext, "太大了", Toast.LENGTH_SHORT).show(); caCert = byteArrayOf() }
}
}
createManagedProfile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if(it.resultCode==Activity.RESULT_CANCELED){Toast.makeText(applicationContext, "用户已取消", Toast.LENGTH_SHORT).show()}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
registerActivityResult()
displayMetrics = applicationContext.resources.displayMetrics
setContent {
OwnDroidTheme {
MyScaffold()
}
}
}
}
@ExperimentalMaterial3Api
@Composable
fun MyScaffold(){
val navCtrl = rememberNavController()
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
val focusMgr = LocalFocusManager.current
SetDarkTheme()
NavHost(
navController = navCtrl,
startDestination = "HomePage",
modifier = Modifier
.fillMaxSize()
.background(bgColor)
.imePadding()
.pointerInput(Unit) {detectTapGestures(onTap = {focusMgr.clearFocus()})},
enterTransition = Animations().navHostEnterTransition,
exitTransition = Animations().navHostExitTransition,
popEnterTransition = Animations().navHostPopEnterTransition,
popExitTransition = Animations().navHostPopExitTransition
){
composable(route = "HomePage", content = { HomePage(navCtrl)})
composable(route = "SystemManage", content = { SystemManage(navCtrl) })
composable(route = "ManagedProfile", content = {ManagedProfile(navCtrl)})
composable(route = "Permissions", content = { DpmPermissions(navCtrl)})
composable(route = "ApplicationManage", content = { ApplicationManage(navCtrl)})
composable(route = "UserRestriction", content = { UserRestriction(navCtrl)})
composable(route = "UserManage", content = { UserManage(navCtrl)})
composable(route = "Password", content = { Password(navCtrl)})
composable(route = "AppSetting", content = { AppSetting(navCtrl)})
composable(route = "Network", content = {Network(navCtrl)})
composable(route = "PackageSelector"){PackageSelector(navCtrl)}
composable(route = "PermissionPicker"){PermissionPicker(navCtrl)}
}
LaunchedEffect(Unit){
val profileInited = sharedPref.getBoolean("ManagedProfileActivated",false)
val profileNotActivated = !profileInited&&isProfileOwner(myDpm)&&(VERSION.SDK_INT<24||(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)))
if(profileNotActivated){
myDpm.setProfileEnabled(myComponent)
sharedPref.edit().putBoolean("ManagedProfileActivated",true).apply()
Toast.makeText(myContext, myContext.getString(R.string.work_profile_activated), Toast.LENGTH_SHORT).show()
}
}
}
@Composable
private fun HomePage(navCtrl:NavHostController){
val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,Receiver::class.java)
val activateType =
if(isDeviceOwner(myDpm)){"Device Owner"}
else if(isProfileOwner(myDpm)){
stringResource(if(VERSION.SDK_INT>=24&&myDpm.isManagedProfile(myComponent)){R.string.work_profile_owner}else{R.string.profile_owner})
}
else if(myDpm.isAdminActive(myComponent)){"Device Admin"}else{""}
Column(modifier = Modifier.statusBarsPadding().verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 25.dp))
Text(text = stringResource(R.string.app_name), style = typography.headlineLarge, modifier = Modifier.padding(start = 10.dp), color = colorScheme.onBackground)
Spacer(Modifier.padding(vertical = 8.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 8.dp)
.clip(RoundedCornerShape(15))
.background(color = colorScheme.primary)
.clickable(onClick = { navCtrl.navigate("Permissions") })
.padding(vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Spacer(modifier = Modifier.padding(start = 22.dp))
Icon(
painter = painterResource(if(myDpm.isAdminActive(myComponent)){ R.drawable.check_circle_fill1 }else{ R.drawable.block_fill0 }),
contentDescription = null,
tint = colorScheme.onPrimary
)
Spacer(modifier = Modifier.padding(start = 10.dp))
Column {
Text(
text = stringResource(if(myDpm.isAdminActive(myComponent)){R.string.activated}else{R.string.deactivated}),
style = typography.headlineSmall,
color = colorScheme.onPrimary,
modifier = Modifier.padding(bottom = 2.dp)
)
if(activateType!=""){ Text(text = activateType, color = colorScheme.onPrimary, modifier = Modifier.padding(start = 2.dp)) }
}
}
HomePageItem(R.string.system_manage, R.drawable.mobile_phone_fill0, "SystemManage", navCtrl)
if(VERSION.SDK_INT>=24&&(isDeviceOwner(myDpm))||isProfileOwner(myDpm)){ HomePageItem(R.string.network, R.drawable.wifi_fill0, "Network",navCtrl) }
if(
(VERSION.SDK_INT<24&&!isDeviceOwner(myDpm))||(
VERSION.SDK_INT>=24&&(myDpm.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)||
(isProfileOwner(myDpm)&&myDpm.isManagedProfile(myComponent)))
)
){
HomePageItem(R.string.work_profile, R.drawable.work_fill0, "ManagedProfile",navCtrl)
}
HomePageItem(R.string.app_manager, R.drawable.apps_fill0, "ApplicationManage", navCtrl)
if(VERSION.SDK_INT>=24){
HomePageItem(R.string.user_restrict, R.drawable.person_off, "UserRestriction", navCtrl)
}
HomePageItem(R.string.user_manager,R.drawable.manage_accounts_fill0,"UserManage",navCtrl)
HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0, "Password",navCtrl)
HomePageItem(R.string.setting, R.drawable.settings_fill0, "AppSetting",navCtrl)
Spacer(Modifier.padding(vertical = 20.dp))
}
}
@Composable
fun HomePageItem(name:Int, imgVector:Int, navTo:String, myNav:NavHostController){
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(25))
.clickable(onClick = { myNav.navigate(navTo) })
.padding(vertical = 13.dp),
verticalAlignment = Alignment.CenterVertically
) {
Spacer(Modifier.padding(start = 30.dp))
Icon(
painter = painterResource(imgVector),
contentDescription = null,
tint = colorScheme.onBackground
)
Spacer(Modifier.padding(start = 15.dp))
Column {
Text(
text = stringResource(name),
style = typography.headlineSmall,
color = colorScheme.onBackground,
modifier = Modifier.padding(bottom = 2.dp)
)
}
}
}

View File

@@ -0,0 +1,96 @@
package com.bintianqi.owndroid
import android.Manifest
import android.os.Build.VERSION
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.dpm.applySelectedPermission
import com.bintianqi.owndroid.dpm.selectedPermission
import com.bintianqi.owndroid.ui.NavIcon
import com.bintianqi.owndroid.ui.theme.bgColor
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PermissionPicker(navCtrl: NavHostController){
Scaffold(
topBar = {
TopAppBar(
title = {Text(text = stringResource(R.string.permission_picker))},
navigationIcon = {NavIcon{navCtrl.navigateUp()}},
colors = TopAppBarDefaults.topAppBarColors(containerColor = bgColor)
)
}
){ paddingValues->
LazyColumn(
modifier = Modifier.fillMaxSize().padding(top = paddingValues.calculateTopPadding()).background(bgColor)
){
items(permissionList()){
Column(
modifier = Modifier
.fillMaxWidth()
.clickable{
selectedPermission = it.first
applySelectedPermission = true
navCtrl.navigateUp()
}
.padding(vertical = 6.dp, horizontal = 8.dp)
){
Text(text = it.first)
Text(text = stringResource(it.second), modifier = Modifier.alpha(0.8F))
}
}
items(1){ Spacer(Modifier.padding(vertical = 30.dp)) }
}
}
}
private fun permissionList():List<Pair<String,Int>>{
val list = mutableListOf<Pair<String,Int>>()
list.add(Pair(Manifest.permission.READ_EXTERNAL_STORAGE,R.string.permission_READ_EXTERNAL_STORAGE))
list.add(Pair(Manifest.permission.WRITE_EXTERNAL_STORAGE,R.string.permission_WRITE_EXTERNAL_STORAGE))
if(VERSION.SDK_INT>=33){
list.add(Pair(Manifest.permission.READ_MEDIA_AUDIO,R.string.permission_READ_MEDIA_AUDIO))
list.add(Pair(Manifest.permission.READ_MEDIA_VIDEO,R.string.permission_READ_MEDIA_VIDEO))
list.add(Pair(Manifest.permission.READ_MEDIA_IMAGES,R.string.permission_READ_MEDIA_IMAGES))
}
list.add(Pair(Manifest.permission.CAMERA,R.string.permission_CAMERA))
list.add(Pair(Manifest.permission.RECORD_AUDIO,R.string.permission_RECORD_AUDIO))
list.add(Pair(Manifest.permission.ACCESS_COARSE_LOCATION,R.string.permission_ACCESS_COARSE_LOCATION))
list.add(Pair(Manifest.permission.ACCESS_FINE_LOCATION,R.string.permission_ACCESS_FINE_LOCATION))
if(VERSION.SDK_INT>=29){
list.add(Pair(Manifest.permission.ACCESS_BACKGROUND_LOCATION,R.string.permission_ACCESS_BACKGROUND_LOCATION))
}
list.add(Pair(Manifest.permission.READ_CONTACTS,R.string.permission_READ_CONTACTS))
list.add(Pair(Manifest.permission.WRITE_CONTACTS,R.string.permission_WRITE_CONTACTS))
list.add(Pair(Manifest.permission.READ_CALENDAR,R.string.permission_READ_CALENDAR))
list.add(Pair(Manifest.permission.WRITE_CALENDAR,R.string.permission_WRITE_CALENDAR))
list.add(Pair(Manifest.permission.CALL_PHONE,R.string.permission_CALL_PHONE))
list.add(Pair(Manifest.permission.READ_PHONE_STATE,R.string.permission_READ_PHONE_STATE))
list.add(Pair(Manifest.permission.READ_SMS,R.string.permission_READ_SMS))
list.add(Pair(Manifest.permission.RECEIVE_SMS,R.string.permission_RECEIVE_SMS))
list.add(Pair(Manifest.permission.SEND_SMS,R.string.permission_SEND_SMS))
list.add(Pair(Manifest.permission.READ_CALL_LOG,R.string.permission_READ_CALL_LOG))
list.add(Pair(Manifest.permission.WRITE_CALL_LOG,R.string.permission_WRITE_CALL_LOG))
list.add(Pair(Manifest.permission.BODY_SENSORS,R.string.permission_BODY_SENSORS))
if(VERSION.SDK_INT>=33){
list.add(Pair(Manifest.permission.BODY_SENSORS_BACKGROUND,R.string.permission_BODY_SENSORS_BACKGROUND))
}
if(VERSION.SDK_INT>29){
list.add(Pair(Manifest.permission.ACTIVITY_RECOGNITION,R.string.permission_ACTIVITY_RECOGNITION))
}
if(VERSION.SDK_INT>=33){
list.add(Pair(Manifest.permission.POST_NOTIFICATIONS,R.string.permission_POST_NOTIFICATIONS))
}
//list.add(Pair(Manifest.permission.,R.string.))
return list
}

View File

@@ -0,0 +1,191 @@
package com.bintianqi.owndroid
import android.graphics.drawable.Drawable
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
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.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.dpm.applySelectedPackage
import com.bintianqi.owndroid.dpm.selectedPackage
import com.bintianqi.owndroid.ui.NavIcon
import com.bintianqi.owndroid.ui.theme.bgColor
import com.google.accompanist.drawablepainter.rememberDrawablePainter
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
private data class PkgInfo(
val pkgName: String,
val label: String,
val icon: Drawable,
val type:String
)
private val pkgs = mutableListOf<PkgInfo>()
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun PackageSelector(navCtrl:NavHostController){
val context = LocalContext.current
val pm = context.packageManager
val apps = pm.getInstalledApplications(0)
var progress by remember{mutableIntStateOf(0)}
var show by remember{mutableStateOf(true)}
var hideProgress by remember{mutableStateOf(true)}
var filter by remember{mutableStateOf("data")}
val scrollState = rememberLazyListState()
val co = rememberCoroutineScope()
val getPkgList:suspend ()->Unit = {
show = false
progress = 0
hideProgress = false
pkgs.clear()
for(pkg in apps){
val srcDir = pkg.sourceDir
pkgs += PkgInfo(
pkg.packageName, pkg.loadLabel(pm).toString(), pkg.loadIcon(pm),
if(srcDir.contains("/data/")){ "data" }
else if(
srcDir.contains("system/priv-app")||srcDir.contains("product/priv-app")||
srcDir.contains("ext/priv-app")||srcDir.contains("vendor/priv-app")
){"priv"}
else if(srcDir.contains("apex")){"apex"}
else{"system"}
)
progress+=1
delay(1)
}
show = true
delay(300)
hideProgress = true
}
Scaffold(
topBar = {
TopAppBar(
title = {
Row(
horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth().padding(bottom = 2.dp),
verticalAlignment = Alignment.CenterVertically
){
Text(text = stringResource(R.string.pkg_selector))
Row {
Icon(
painter = painterResource(R.drawable.filter_alt_fill0),
contentDescription = "filter",
modifier = Modifier
.padding(horizontal = 6.dp)
.clip(RoundedCornerShape(50))
.combinedClickable(
onClick = {
when(filter){
"data"-> {
filter = "system"; co.launch {scrollState.scrollToItem(0)}
Toast.makeText(context, context.getString(R.string.show_system_app), Toast.LENGTH_SHORT).show()
}
"system"-> {
filter = "priv"; co.launch {scrollState.scrollToItem(0)}
Toast.makeText(context, context.getString(R.string.show_priv_app), Toast.LENGTH_SHORT).show()
}
else-> {
filter = "data"; co.launch {scrollState.scrollToItem(0)}
Toast.makeText(context, context.getString(R.string.show_user_app), Toast.LENGTH_SHORT).show()
}
}
},
onLongClick = {
filter = "apex"
Toast.makeText(context, context.getString(R.string.show_apex_app), Toast.LENGTH_SHORT).show()
}
)
.padding(5.dp)
)
Icon(
painter = painterResource(R.drawable.refresh_fill0),
contentDescription = "refresh",
modifier = Modifier
.padding(horizontal = 6.dp)
.clip(RoundedCornerShape(50))
.clickable{
co.launch{
delay(100)
getPkgList()
}
}
.padding(5.dp)
)
}
}
},
navigationIcon = {NavIcon{navCtrl.navigateUp()}},
colors = TopAppBarDefaults.topAppBarColors(containerColor = bgColor)
)
}
){paddingValues->
LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize().background(bgColor).padding(top = paddingValues.calculateTopPadding()),
state = scrollState
){
items(1){
AnimatedVisibility(!hideProgress) {
LinearProgressIndicator(progress = {progress.toFloat()/apps.size}, modifier = Modifier.fillMaxWidth())
}
}
if(show) {
items(pkgs) {
if(filter==it.type){
PackageItem(it, navCtrl)
}
}
items(1){Spacer(Modifier.padding(vertical = 30.dp))}
}else{
items(1){
Spacer(Modifier.padding(top = 5.dp))
Text(text = stringResource(R.string.loading), modifier = Modifier.alpha(0.8F))
}
}
}
LaunchedEffect(Unit){
delay(250)
if(pkgs.size==0){getPkgList()}
}
}
}
@Composable
private fun PackageItem(pkg: PkgInfo, navCtrl: NavHostController){
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable{selectedPackage =pkg.pkgName;applySelectedPackage =true;navCtrl.navigateUp()}
.padding(vertical = 3.dp)
){
Spacer(Modifier.padding(start = 15.dp))
Image(
painter = rememberDrawablePainter(pkg.icon), contentDescription = "App icon",
modifier = Modifier.size(50.dp)
)
Spacer(Modifier.padding(start = 15.dp))
Column {
Text(text = pkg.label, style = typography.titleLarge)
Text(text = pkg.pkgName, modifier = Modifier.alpha(0.8F))
Spacer(Modifier.padding(top = 3.dp))
}
}
}

View File

@@ -0,0 +1,83 @@
package com.bintianqi.owndroid
import android.annotation.SuppressLint
import android.app.admin.DeviceAdminReceiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller.*
import android.os.Build.VERSION
import android.os.PersistableBundle
import android.util.Log
import android.widget.Toast
class Receiver : DeviceAdminReceiver() {
override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent)
Toast.makeText(context, "已启用", Toast.LENGTH_SHORT).show()
}
override fun onTransferOwnershipComplete(context: Context, bundle: PersistableBundle?) {
super.onTransferOwnershipComplete(context, bundle)
if(bundle!=null){
Toast.makeText(context,"转移所有权完毕,附加内容长度:${bundle.size()}",Toast.LENGTH_SHORT).show()
Log.d("TransferOwnerShip",bundle.toString())
}else{
Toast.makeText(context,"转移所有权完毕,无附加内容}",Toast.LENGTH_SHORT).show()
}
}
override fun onProfileProvisioningComplete(context: Context, intent: Intent) {
super.onProfileProvisioningComplete(context, intent)
Toast.makeText(context, "创建工作资料完成", Toast.LENGTH_SHORT).show()
}
@SuppressLint("UnsafeProtectedBroadcastReceiver")
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
}
override fun onNetworkLogsAvailable(context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int) {
super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount)
Toast.makeText(context,"可以收集网络日志",Toast.LENGTH_SHORT).show()
Log.e("","网络日志可用")
}
override fun onSecurityLogsAvailable(context: Context, intent: Intent) {
super.onSecurityLogsAvailable(context, intent)
Toast.makeText(context,"可以收集安全日志",Toast.LENGTH_SHORT).show()
Log.e("","安全日志可用")
}
override fun onDisableRequested(context: Context, intent: Intent): CharSequence {
Toast.makeText(context, "撤销授权", Toast.LENGTH_SHORT).show()
return "这是取消时的提示"
}
override fun onDisabled(context: Context, intent: Intent) {
super.onDisabled(context, intent)
Toast.makeText(context, "已禁用", Toast.LENGTH_SHORT).show()
}
override fun onSystemUpdatePending(context: Context, intent: Intent, receivedTime: Long) {
if (VERSION.SDK_INT < 26) { return }
Toast.makeText(context, "新的系统更新!", Toast.LENGTH_SHORT).show()
}
}
class PackageInstallerReceiver:BroadcastReceiver(){
override fun onReceive(context: Context, intent: Intent) {
val toastText = when(intent.getIntExtra(EXTRA_STATUS,666)){
STATUS_PENDING_USER_ACTION->"等待用户交互"
STATUS_SUCCESS->"成功"
STATUS_FAILURE->"失败"
STATUS_FAILURE_BLOCKED->"失败:被阻止"
STATUS_FAILURE_ABORTED->"失败:被打断"
STATUS_FAILURE_INVALID->"失败:无效"
STATUS_FAILURE_CONFLICT->"失败:冲突"
STATUS_FAILURE_STORAGE->"失败:空间不足"
STATUS_FAILURE_INCOMPATIBLE->"失败:不兼容"
STATUS_FAILURE_TIMEOUT->"失败:超时"
else->"未知"
}
Log.e("静默安装","${intent.getIntExtra(EXTRA_STATUS,666)}:$toastText")
}
}

View File

@@ -0,0 +1,118 @@
package com.bintianqi.owndroid
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme.colorScheme
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.graphics.Color
import androidx.compose.ui.graphics.toArgb
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.ui.*
import com.bintianqi.owndroid.ui.theme.SetDarkTheme
import com.bintianqi.owndroid.ui.theme.bgColor
@Composable
fun AppSetting(navCtrl:NavHostController){
val localNavCtrl = rememberNavController()
val backStackEntry by localNavCtrl.currentBackStackEntryAsState()
/*val titleMap = mapOf(
"About" to R.string.about
)*/
Scaffold(
topBar = {
/*TopAppBar(
title = {Text(text = stringResource(titleMap[backStackEntry?.destination?.route]?:R.string.setting))},
navigationIcon = {NavIcon{if(backStackEntry?.destination?.route=="Home"){navCtrl.navigateUp()}else{localNavCtrl.navigateUp()}}},
colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.surfaceVariant)
)*/
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 = "Settings"){Settings()}
composable(route = "About"){About()}
}
}
}
@Composable
private fun Home(navCtrl: NavHostController){
Column(modifier = Modifier.fillMaxSize()){
SubPageItem(R.string.setting,"",R.drawable.settings_fill0){navCtrl.navigate("Settings")}
SubPageItem(R.string.about,"",R.drawable.info_fill0){navCtrl.navigate("About")}
}
}
@Composable
private fun Settings(){
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
SetDarkTheme()
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
SwitchItem(
R.string.dynamic_color, stringResource(R.string.dynamic_color_desc),null,
{sharedPref.getBoolean("dynamicColor",false)},{sharedPref.edit().putBoolean("dynamicColor",it).apply()}
)
if(colorScheme.background.toArgb()!=Color(0xFF000000).toArgb()){
SwitchItem(
R.string.blackTheme, stringResource(R.string.blackTheme_desc),null,
{sharedPref.getBoolean("blackTheme",false)},{sharedPref.edit().putBoolean("blackTheme",it).apply()}
)
}
Box(modifier = Modifier.padding(10.dp)){
Information {
Text(text = stringResource(R.string.need_relaunch))
}
}
}
}
@Composable
private fun About(){
val myContext = LocalContext.current
val pkgInfo = myContext.packageManager.getPackageInfo(myContext.packageName,0)
val verCode = pkgInfo.versionCode
val verName = pkgInfo.versionName
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())){
Spacer(Modifier.padding(vertical = 10.dp))
Column(modifier = Modifier.padding(horizontal = 8.dp)){
Text(text = stringResource(R.string.about), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
Text(text = stringResource(R.string.app_name)+" v$verName ($verCode)")
Text(text = stringResource(R.string.about_desc))
Spacer(Modifier.padding(vertical = 5.dp))
}
SubPageItem(R.string.user_guide,"",R.drawable.open_in_new){shareLink(myContext, "https://github.com/BinTianqi/AndroidOwner/blob/master/Guide.md")}
SubPageItem(R.string.source_code,"",R.drawable.open_in_new){shareLink(myContext, "https://github.com/BinTianqi/AndroidOwner")}
}
}
fun shareLink(inputContext:Context,link:String){
val uri = Uri.parse(link)
val intent = Intent(Intent.ACTION_VIEW, uri)
inputContext.startActivity(Intent.createChooser(intent, "Open in browser"),null)
}

View File

@@ -0,0 +1,69 @@
package com.bintianqi.owndroid
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build.VERSION
import android.widget.Toast
import androidx.core.content.ContextCompat.startActivity
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
fun uriToStream(
context: Context,
uri: Uri?,
operation:(stream: InputStream)->Unit
){
if(uri!=null){
try{
val stream = context.contentResolver.openInputStream(uri)
if(stream!=null) { operation(stream) }
else{ Toast.makeText(context, "空的流", Toast.LENGTH_SHORT).show() }
stream?.close()
}
catch(e: FileNotFoundException){ Toast.makeText(context, "文件不存在", Toast.LENGTH_SHORT).show() }
catch(e: IOException){ Toast.makeText(context, "IO异常", Toast.LENGTH_SHORT).show() }
}else{ Toast.makeText(context, "空URI", Toast.LENGTH_SHORT).show() }
}
fun List<Any>.toText():String{
var output = ""
var isFirst = true
for(each in listIterator()){
if(isFirst){isFirst=false}else{output+="\n"}
output+=each
}
return output
}
fun Set<Any>.toText():String{
var output = ""
var isFirst = true
for(each in iterator()){
if(isFirst){isFirst=false}else{output+="\n"}
output+=each
}
return output
}
fun writeClipBoard(context: Context, string: String):Boolean{
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
try {
if(VERSION.SDK_INT>=23){
val hasPermission: Boolean = clipboardManager.hasPrimaryClip()
if(!hasPermission) {
val intent = Intent(android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS)
intent.setData(Uri.parse("package:"+context.packageName))
startActivity(context,intent,null)
}
}
clipboardManager.setPrimaryClip(ClipData.newPlainText("", string))
}catch(e:Exception){
return false
}
return true
}

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

View File

@@ -0,0 +1,59 @@
package com.bintianqi.owndroid.ui
import androidx.compose.animation.*
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.core.spring
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.navigation.NavBackStackEntry
import com.bintianqi.owndroid.displayMetrics
class Animations{
private val fade: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow)
private val spring:FiniteAnimationSpec<IntOffset> = spring(stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold)
val animateListSize:FiniteAnimationSpec<IntSize> = spring(stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntSize.VisibilityThreshold)
private val screenWidth = displayMetrics.widthPixels
private val initialOffsetValue = screenWidth/10
private val targetOffsetValue = screenWidth/10
val navHostEnterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
fadeIn(animationSpec = fade) +
slideIntoContainer(
animationSpec = spring,
towards = AnimatedContentTransitionScope.SlideDirection.End,
initialOffset = {initialOffsetValue}
)
}
val navHostExitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
fadeOut(animationSpec = fade) +
slideOutOfContainer(
animationSpec = spring,
towards = AnimatedContentTransitionScope.SlideDirection.Start,
targetOffset = {-targetOffsetValue}
)
}
val navHostPopEnterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
fadeIn(animationSpec = fade) +
slideIntoContainer(
animationSpec = spring,
towards = AnimatedContentTransitionScope.SlideDirection.End,
initialOffset = {-initialOffsetValue}
)
}
val navHostPopExitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
fadeOut(animationSpec = fade) +
slideOutOfContainer(
animationSpec = spring,
towards = AnimatedContentTransitionScope.SlideDirection.Start,
targetOffset = {targetOffsetValue}
)
}
}

View File

@@ -0,0 +1,192 @@
package com.bintianqi.owndroid.ui
import android.content.Context
import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.ui.theme.bgColor
import com.bintianqi.owndroid.writeClipBoard
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
fun SubPageItem(
@StringRes title: Int,
desc:String,
@DrawableRes icon: Int? = null,
operation: () -> Unit
){
Row(
modifier = Modifier.fillMaxWidth().clickable(onClick = operation).padding(vertical = 15.dp),
verticalAlignment = Alignment.CenterVertically
){
Spacer(Modifier.padding(start = 30.dp))
if(icon!=null){
Icon(painter = painterResource(icon), contentDescription = stringResource(title), modifier = Modifier.padding(top = 1.dp))
Spacer(Modifier.padding(start = 15.dp))
}
Column {
Text(text = stringResource(title), style = typography.titleLarge, modifier = Modifier.padding(bottom = 1.dp))
if(desc!=""){Text(text = desc, color = colorScheme.onBackground.copy(alpha = 0.8F))}
}
}
}
@Composable
fun NavIcon(operation: () -> Unit){
Icon(
painter = painterResource(R.drawable.arrow_back_fill0),
contentDescription = "Back arrow",
modifier = Modifier
.padding(horizontal = 6.dp)
.clip(RoundedCornerShape(50))
.clickable(onClick = operation)
.padding(5.dp)
)
}
@Composable
fun Information(content: @Composable ()->Unit){
Column(modifier = Modifier.fillMaxWidth().padding(start = 5.dp)){
Icon(painter = painterResource(R.drawable.info_fill0),contentDescription = "info", tint = colorScheme.onBackground.copy(alpha = 0.8F))
Spacer(Modifier.padding(vertical = 1.dp))
Row {
Spacer(Modifier.padding(horizontal = 1.dp))
content()
}
}
}
@Composable
fun RadioButtonItem(
text:String,
selected:()->Boolean,
operation:()->Unit,
textColor: Color = colorScheme.onBackground
){
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
val isWear = sharedPref.getBoolean("isWear",false)
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier
.fillMaxWidth()
.padding(vertical = if(isWear){3.dp}else{0.dp})
.clip(RoundedCornerShape(25))
.clickable(onClick = operation)
) {
RadioButton(selected = selected(), onClick = operation,modifier=if(isWear){Modifier.size(28.dp)}else{Modifier})
Text(text = text, style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}, color = textColor,
modifier = Modifier.padding(bottom = 2.dp))
}
}
@Composable
fun CheckBoxItem(
text:String,
checked:()->Boolean,
operation:()->Unit,
textColor:Color = colorScheme.onBackground
){
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
val isWear = sharedPref.getBoolean("isWear",false)
Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier
.fillMaxWidth()
.padding(vertical = if(isWear){3.dp}else{0.dp})
.clip(RoundedCornerShape(25))
.clickable(onClick = operation)
) {
Checkbox(
checked = checked(),
onCheckedChange = {operation()},
modifier=if(isWear){Modifier.size(28.dp)}else{Modifier}
)
Text(text = text, style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}, color = textColor, modifier = Modifier.padding(bottom = 2.dp))
}
}
@Composable
fun SwitchItem(
@StringRes title: Int,
desc: String,
@DrawableRes icon: Int?,
getState: ()->Boolean,
onCheckedChange: (Boolean)->Unit,
enable:Boolean=true
){
var checked by remember{mutableStateOf(false)}
checked = getState()
Box(modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.align(Alignment.CenterStart)){
Spacer(Modifier.padding(start = 30.dp))
if(icon!=null){
Icon(painter = painterResource(icon),contentDescription = null)
Spacer(Modifier.padding(start = 15.dp))
}
Column(modifier = Modifier.padding(end = 60.dp)){
Text(text = stringResource(title), style = typography.titleLarge)
if(desc!=""){Text(text = desc, color = colorScheme.onBackground.copy(alpha = 0.8F))}else{Spacer(Modifier.padding(vertical = 1.dp))}
}
}
Switch(
checked = checked, onCheckedChange = {onCheckedChange(it);checked=getState()},
modifier = Modifier.align(Alignment.CenterEnd).padding(end = 12.dp), enabled = enable
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopBar(
backStackEntry:NavBackStackEntry?,
navCtrl:NavHostController,
localNavCtrl:NavHostController,
title:@Composable ()->Unit = {}
){
TopAppBar(
//Text(text = stringResource(titleMap[backStackEntry?.destination?.route]?:R.string.user_restrict))
title = title,
navigationIcon = {NavIcon{if(backStackEntry?.destination?.route=="Home"){navCtrl.navigateUp()}else{localNavCtrl.navigateUp()}}},
colors = TopAppBarDefaults.topAppBarColors(containerColor = bgColor)
)
}
@Composable
fun CopyTextButton(context: Context, @StringRes label: Int, content: String){
var ok by remember{mutableStateOf(false)}
val scope = rememberCoroutineScope()
Button(
onClick = {
if(!ok){
scope.launch{
if(writeClipBoard(context,content)){ ok = true; delay(2000); ok = false }
else{ Toast.makeText(context,context.getString(R.string.fail),Toast.LENGTH_SHORT).show() }
}
}
}
){
Row(
verticalAlignment = Alignment.CenterVertically, modifier = Modifier.animateContentSize()
){
Icon(painter = painterResource(if(ok){R.drawable.check_fill0}else{R.drawable.content_copy_fill0}),contentDescription = null)
Spacer(modifier = Modifier.padding(horizontal = 2.dp))
Text(text = stringResource(if(ok){R.string.success}else{label}))
}
}
}

View File

@@ -0,0 +1,63 @@
package com.bintianqi.owndroid.ui.theme
import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF006A65)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFF70F7EE)
val md_theme_light_onPrimaryContainer = Color(0xFF00201E)
val md_theme_light_secondary = Color(0xFF4A6361)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFCCE8E5)
val md_theme_light_onSecondaryContainer = Color(0xFF051F1E)
val md_theme_light_tertiary = Color(0xFF48607B)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFD0E4FF)
val md_theme_light_onTertiaryContainer = Color(0xFF001D34)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFAFDFB)
val md_theme_light_onBackground = Color(0xFF191C1C)
val md_theme_light_surface = Color(0xFFFAFDFB)
val md_theme_light_onSurface = Color(0xFF191C1C)
val md_theme_light_surfaceVariant = Color(0xFFDAE5E3)
val md_theme_light_onSurfaceVariant = Color(0xFF3F4947)
val md_theme_light_outline = Color(0xFF6F7978)
val md_theme_light_inverseOnSurface = Color(0xFFEFF1F0)
val md_theme_light_inverseSurface = Color(0xFF2D3130)
val md_theme_light_inversePrimary = Color(0xFF4FDAD1)
val md_theme_light_surfaceTint = Color(0xFF006A65)
val md_theme_light_outlineVariant = Color(0xFFBEC9C7)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFF4FDAD1)
val md_theme_dark_onPrimary = Color(0xFF003734)
val md_theme_dark_primaryContainer = Color(0xFF00504C)
val md_theme_dark_onPrimaryContainer = Color(0xFF70F7EE)
val md_theme_dark_secondary = Color(0xFFB0CCC9)
val md_theme_dark_onSecondary = Color(0xFF1C3533)
val md_theme_dark_secondaryContainer = Color(0xFF324B49)
val md_theme_dark_onSecondaryContainer = Color(0xFFCCE8E5)
val md_theme_dark_tertiary = Color(0xFFB0C9E7)
val md_theme_dark_onTertiary = Color(0xFF18324A)
val md_theme_dark_tertiaryContainer = Color(0xFF304962)
val md_theme_dark_onTertiaryContainer = Color(0xFFD0E4FF)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF000000)
val md_theme_dark_onBackground = Color(0xFFE0E3E2)
val md_theme_dark_surface = Color(0xFF191C1C)
val md_theme_dark_onSurface = Color(0xFFE0E3E2)
val md_theme_dark_surfaceVariant = Color(0xFF3F4947)
val md_theme_dark_onSurfaceVariant = Color(0xFFBEC9C7)
val md_theme_dark_outline = Color(0xFF889391)
val md_theme_dark_inverseOnSurface = Color(0xFF191C1C)
val md_theme_dark_inverseSurface = Color(0xFFE0E3E2)
val md_theme_dark_inversePrimary = Color(0xFF006A65)
val md_theme_dark_surfaceTint = Color(0xFF4FDAD1)
val md_theme_dark_outlineVariant = Color(0xFF3F4947)
val md_theme_dark_scrim = Color(0xFF000000)

View File

@@ -0,0 +1,129 @@
package com.bintianqi.owndroid.ui.theme
import android.app.Activity
import android.content.Context
import android.os.Build
import android.os.Build.VERSION
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.*
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inverseSurface = md_theme_dark_inverseSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim
)
private val LightColorScheme = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline,
inverseOnSurface = md_theme_light_inverseOnSurface,
inverseSurface = md_theme_light_inverseSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
outlineVariant = md_theme_light_outlineVariant,
scrim = md_theme_light_scrim
)
var bgColor = Color(0xFF000000)
@Composable
fun SetDarkTheme(){
val dark = isSystemInDarkTheme()
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
val bg = colorScheme.background
val lightBg = colorScheme.primary.copy(alpha = 0.05F)
bgColor = if(dark){
if(sharedPref.getBoolean("blackTheme",true)){ Color(0xFF000000) }else{ bg }
}else{
lightBg
}
}
@Composable
fun OwnDroidTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
SetDarkTheme()
val context = LocalContext.current
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
if(!sharedPref.contains("dynamicColor")&&VERSION.SDK_INT>=32){
sharedPref.edit().putBoolean("dynamicColor",true).apply()
}
val dynamicColor = sharedPref.getBoolean("dynamicColor",false)
val colorScheme = when {
dynamicColor && VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = Color.Transparent.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@@ -0,0 +1,34 @@
package com.bintianqi.owndroid.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)