add user manage section

This commit is contained in:
BinTianqi
2024-01-21 20:13:44 +08:00
parent 1f52a8920c
commit 01c0a119a6
12 changed files with 492 additions and 164 deletions

View File

@@ -1,26 +1,46 @@
# 简介
# Android Owner
使用安卓的DeviceAdmin和DeviceOwner API管理你的设备
### 简介
欢迎大家提交Issue
使用安卓的Device Admin和Device Owner全方位掌控你的设备。
这个应用使用Kotlin + Jetpack Compose有不完全的质感设计3配色
记得给我个小星星欢迎大家提交Issue
### 功能
- 添加用户限
-止使用蓝牙
- 禁止使用WiFi
- 禁止安装应用
- ......
- 设备控
-用相机
- 禁止截屏
- 全局静音
- 关闭USB信号需设备支持
- 管理系统更新策略
- 清除数据
- 管理应用
- 隐藏应用
- 停用应用
- 禁止卸载应用
- ......
- 设备控制
- 禁用相机
- 禁止截屏
- 隐藏状态栏
- 用户限制
- 网络和互联网禁止使用流量、WiFi、VPN、私人DNS
- 其他连接禁止使用蓝牙、位置信息、NFC、USB(MTP)
- 应用:禁止安装卸载应用、禁止清除应用的存储空间
- 显示与音量:禁止调整亮度、音量
- 用户和工作资料:禁止添加/切换/移除用户,禁止添加/移除工作资料
- 杂项:禁止自动填充服务、禁止调试
- 密码
- 修改密码
- 最大密码错误次数
- 密码失效超时时间
这个应用需要的权限十分敏感,如果操作不慎,可能会让你的设备丢失数据或者让你无法解锁你的设备
### 这个应用十分危险!!!
但是你无需担心,这个应用不是恶意软件
在使用各个功能之前,请仔细阅读相应的说明。红色的按钮一定要谨慎使用!
如果操作不慎,可能会意外地丢失数据或者让你无法解锁你的设备!
### 即将加入的功能
- Profile Owner相关
- Managed Profile工作资料和多用户相关
- 安装/卸载应用,清除应用的缓存和存储空间
- 应用管理的包选择器(目前只能手动输入包名)
- 适配手表

View File

@@ -11,8 +11,8 @@ android {
applicationId = "com.binbin.androidowner"
minSdk = 23
targetSdk = 34
versionCode = 4
versionName = "1.3"
versionCode = 5
versionName = "1.4"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@@ -1,9 +1,12 @@
package com.binbin.androidowner
import android.app.PendingIntent
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager.NameNotFoundException
import android.net.Uri
import android.os.Build.VERSION
@@ -32,6 +35,8 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
import java.io.IOException
import java.io.InputStream
@Composable
@@ -50,7 +55,9 @@ fun ApplicationManage(myDpm:DevicePolicyManager, myComponent:ComponentName,myCon
pkgName = it
},
label = { Text("包名") },
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
)
if(VERSION.SDK_INT>=24){
val isSuspended = {
@@ -80,20 +87,20 @@ fun ApplicationManage(myDpm:DevicePolicyManager, myComponent:ComponentName,myCon
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceAround
) {
Button(onClick = {myDpm.setUninstallBlocked(myComponent,pkgName,false)}) {
Button(onClick = {myDpm.setUninstallBlocked(myComponent,pkgName,false)}, enabled = isDeviceOwner(myDpm)) {
Text("取消防卸载")
}
Button(onClick = {myDpm.setUninstallBlocked(myComponent,pkgName,true)}) {
Button(onClick = {myDpm.setUninstallBlocked(myComponent,pkgName,true)}, enabled = isDeviceOwner(myDpm)) {
Text("防卸载")
}
}
Button(
/*Button(
onClick = {
uninstallApp(myContext,pkgName)
uninstallPkg(pkgName,myContext)
}) {
Text("卸载")
}
Spacer(Modifier.padding(5.dp))
}*/
Spacer(Modifier.padding(20.dp))
}
}
@@ -157,3 +164,39 @@ private fun uninstallApp(context: Context, packageName: String) {
uninstallIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(uninstallIntent)
}
fun isAppInstalled(context: Context, packageName: String): Boolean {
return try {
context.packageManager.getApplicationInfo(packageName, 0)
true
} catch (e: NameNotFoundException) {
false
}
}
@Throws(IOException::class)
fun installPackage(context: Context, inputStream: InputStream, packageName: String?): Boolean {
val packageInstaller = context.packageManager.packageInstaller
val params = PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL
)
params.setAppPackageName(packageName)
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()
session.commit(createIntentSender(context, sessionId))
return true
}
private fun createIntentSender(context: Context, sessionId: Int): IntentSender {
val pendingIntent = PendingIntent.getBroadcast(context, sessionId, Intent(), PendingIntent.FLAG_IMMUTABLE)
return pendingIntent.intentSender
}

View File

@@ -4,6 +4,7 @@ import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.os.Build.VERSION
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -34,6 +35,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@Composable
@@ -71,27 +73,60 @@ fun DeviceControl(myDpm: DevicePolicyManager, myComponent: ComponentName,myConte
.padding(horizontal = 6.dp, vertical = 4.dp)
.clip(RoundedCornerShape(15))
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(8.dp),
.padding(vertical = 5.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(onClick = {myDpm.setKeyguardDisabled(myComponent,true)}) {
Button(
onClick = {
if(myDpm.setKeyguardDisabled(myComponent,true)){
Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show()
}
},
enabled = isDeviceOwner(myDpm)
) {
Text("禁用锁屏(需无密码)")
}
Spacer(Modifier.padding(horizontal = 5.dp))
Button(onClick = {myDpm.setKeyguardDisabled(myComponent,false)}) {
Button(
onClick = {
if(myDpm.setKeyguardDisabled(myComponent,false)){
Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show()
}
},
enabled = isDeviceOwner(myDpm)
) {
Text("启用锁屏")
}
}
Row(
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp, horizontal = 6.dp)
.clip(RoundedCornerShape(15))
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(vertical = 4.dp),
) {
if(VERSION.SDK_INT>=24){
Button(onClick = {myDpm.reboot(myComponent)}) {
Button(onClick = {myDpm.reboot(myComponent)}, enabled = isDeviceOwner(myDpm)) {
Text("重启")
}
Button(onClick = {myDpm.lockNow()}, enabled = myDpm.isAdminActive(myComponent)) {
Text("锁屏")
}
}
}
if(VERSION.SDK_INT>=24){
val wifimac = try {
myDpm.getWifiMacAddress(myComponent).toString()
}catch(e:SecurityException){
"没有权限"
}
Text("WiFi MAC: $wifimac")
Text(text = "WiFi MAC: $wifimac",modifier=Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
}
if(VERSION.SDK_INT<24){
Text("重启和WiFi Mac需要API24")
@@ -106,10 +141,7 @@ fun DeviceControl(myDpm: DevicePolicyManager, myComponent: ComponentName,myConte
if(VERSION.SDK_INT<34){
Text("隐藏状态栏需要API34")
}
Button(onClick = {myDpm.lockNow()}) {
Text("锁屏")
}
Button(onClick = {myDpm.uninstallAllUserCaCerts(myComponent)}) {
Button(onClick = {myDpm.uninstallAllUserCaCerts(myComponent)},modifier = Modifier.align(Alignment.CenterHorizontally), enabled = isDeviceOwner(myDpm)) {
Text(text = "清除用户Ca证书")
}
SysUpdatePolicy(myDpm,myComponent,myContext)
@@ -135,7 +167,8 @@ fun DeviceControl(myDpm: DevicePolicyManager, myComponent: ComponentName,myConte
colors = ButtonDefaults.buttonColors(
containerColor = if(confirmed){MaterialTheme.colorScheme.primary}else{MaterialTheme.colorScheme.error},
contentColor = if(confirmed){MaterialTheme.colorScheme.onPrimary}else{MaterialTheme.colorScheme.onError}
)
),
enabled = isDeviceOwner(myDpm)
) {
Text(text = if(confirmed){"取消"}else{"确定"})
}
@@ -165,6 +198,7 @@ fun DeviceControl(myDpm: DevicePolicyManager, myComponent: ComponentName,myConte
}
}
}
Spacer(Modifier.padding(vertical = 20.dp))
}
}
@@ -207,7 +241,7 @@ private fun DeviceCtrlItem(
}
}
}
if(myDpm.isDeviceOwnerApp("com.binbin.androidowner")){
if(isDeviceOwner(myDpm)){
isEnabled = getMethod()
}
Switch(
@@ -216,7 +250,7 @@ private fun DeviceCtrlItem(
setMethod(!isEnabled)
isEnabled=getMethod()
},
enabled = myDpm.isDeviceOwnerApp("com.binbin.androidowner")
enabled = isDeviceOwner(myDpm)
)
}
}

View File

@@ -2,7 +2,6 @@ package com.binbin.androidowner
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.app.admin.SystemUpdatePolicy
import android.content.ComponentName
import android.content.Context
import android.os.Bundle
@@ -78,7 +77,7 @@ fun MyScaffold(mainDpm:DevicePolicyManager, mainComponent:ComponentName, mainCon
"HomePage" to R.string.app_name,
"DeviceControl" to R.string.device_ctrl,
"Permissions" to R.string.permission,
"UIControl" to R.string.ui_ctrl,
"UserManage" to R.string.user_manage,
"ApplicationManage" to R.string.app_manage,
"UserRestriction" to R.string.user_restrict,
"Password" to R.string.password
@@ -130,6 +129,7 @@ fun MyScaffold(mainDpm:DevicePolicyManager, mainComponent:ComponentName, mainCon
composable(route = "Permissions", content = { DpmPermissions(mainDpm,mainComponent,mainContext,navCtrl)})
composable(route = "ApplicationManage", content = { ApplicationManage(mainDpm,mainComponent,mainContext)})
composable(route = "UserRestriction", content = { UserRestriction(mainDpm,mainComponent)})
composable(route = "UserManage", content = { UserManage(mainDpm,mainComponent,mainContext)})
composable(route = "Password", content = { Password(mainDpm,mainComponent,mainContext)})
}
}
@@ -179,6 +179,7 @@ fun HomePage(navCtrl:NavHostController,myDpm:DevicePolicyManager,myComponent:Com
HomePageItem(R.string.device_ctrl, R.drawable.mobile_phone_fill0, R.string.device_ctrl_desc, "DeviceControl", navCtrl)
HomePageItem(R.string.app_manage, R.drawable.apps_fill0, R.string.apps_ctrl_description, "ApplicationManage", navCtrl)
HomePageItem(R.string.user_restrict, R.drawable.manage_accounts_fill0, R.string.user_restrict_desc, "UserRestriction", navCtrl)
HomePageItem(R.string.user_manage,R.drawable.account_circle_fill0,R.string.user_manage_desc,"UserManage",navCtrl)
HomePageItem(R.string.password, R.drawable.password_fill0,R.string.security_desc, "Password",navCtrl)
}
}
@@ -230,3 +231,7 @@ fun RadioButtonItem(
Text(text)
}
}
fun isDeviceOwner(dpm:DevicePolicyManager): Boolean {
return dpm.isDeviceOwnerApp("com.binbin.androidowner")
}

View File

@@ -12,9 +12,11 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
@@ -25,6 +27,8 @@ import androidx.compose.material.icons.outlined.Check
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
@@ -37,6 +41,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
@@ -105,7 +110,8 @@ fun Password(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext:Conte
if(myDpm.clearResetPasswordToken(myComponent)){ Toast.makeText(myContext, "清除成功", Toast.LENGTH_SHORT).show()
}else{ Toast.makeText(myContext, "清除失败", Toast.LENGTH_SHORT).show() }
},
modifier = Modifier.padding(end = 8.dp)
modifier = Modifier.padding(end = 8.dp),
enabled = isDeviceOwner(myDpm)
) {
Text("清除")
}
@@ -114,7 +120,8 @@ fun Password(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext:Conte
if(myDpm.setResetPasswordToken(myComponent, myByteArray)){ Toast.makeText(myContext, "设置成功", Toast.LENGTH_SHORT).show()
}else{ Toast.makeText(myContext, "设置失败", Toast.LENGTH_SHORT).show() }
},
modifier = Modifier.padding(end = 8.dp)
modifier = Modifier.padding(end = 8.dp),
enabled = isDeviceOwner(myDpm)
) {
Text("设置")
}
@@ -124,12 +131,14 @@ fun Password(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext:Conte
try{ activateToken(myContext)
}catch(e:NullPointerException){ Toast.makeText(myContext, "请先设置令牌", Toast.LENGTH_SHORT).show() }
}else{ Toast.makeText(myContext, "已经激活", Toast.LENGTH_SHORT).show() }
}
},
enabled = isDeviceOwner(myDpm)
) {
Text("激活")
}
}
Text("没有密码时会自动激活令牌")
Text("有可能无法设置密码重置令牌因机而异AVD上能用")
}
}
Column(
@@ -143,10 +152,10 @@ fun Password(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext:Conte
TextField(
value = newPwd,
onValueChange = {newPwd=it},
enabled = !confirmed,
enabled = !confirmed&& isDeviceOwner(myDpm),
label = { Text("密码")},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()})
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
)
Text(text = stringResource(R.string.reset_pwd_desc), modifier = Modifier.padding(vertical = 5.dp))
Row {
@@ -155,7 +164,8 @@ fun Password(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext:Conte
if(newPwd.length>=4||newPwd.isEmpty()){ confirmed=!confirmed
}else{ Toast.makeText(myContext, "需要4位数字或字母", Toast.LENGTH_SHORT).show() }
},
modifier = Modifier.padding(end = 10.dp)
modifier = Modifier.padding(end = 10.dp),
enabled = isDeviceOwner(myDpm)
) {
Text("确认密码")
}
@@ -187,13 +197,13 @@ fun Password(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext:Conte
}
}
}
PasswordItem(R.string.max_pwd_fail,R.string.max_pwd_fail_desc,R.string.max_pwd_fail_textfield, focusMgr,false,
PasswordItem(R.string.max_pwd_fail,R.string.max_pwd_fail_desc,R.string.max_pwd_fail_textfield, myDpm,focusMgr,false,
{myDpm.getMaximumFailedPasswordsForWipe(null).toString()},{ic -> myDpm.setMaximumFailedPasswordsForWipe(myComponent, ic.toInt()) })
PasswordItem(R.string.pwd_timeout,R.string.pwd_timeout_desc,R.string.pwd_timeout_textfield, focusMgr,true,
PasswordItem(R.string.pwd_timeout,R.string.pwd_timeout_desc,R.string.pwd_timeout_textfield, myDpm,focusMgr,true,
{myDpm.getPasswordExpiration(null).toString()},{ic -> myDpm.setPasswordExpirationTimeout(myComponent, ic.toLong()) })
PasswordItem(R.string.pwd_history,R.string.pwd_history_desc,R.string.pwd_history_textfield, focusMgr,true,
PasswordItem(R.string.pwd_history,R.string.pwd_history_desc,R.string.pwd_history_textfield,myDpm, focusMgr,true,
{myDpm.getPasswordHistoryLength(null).toString()},{ic -> myDpm.setPasswordHistoryLength(myComponent, ic.toInt()) })
Spacer(Modifier.padding(vertical = 20.dp))
}
}
@@ -202,6 +212,7 @@ fun PasswordItem(
itemName:Int,
itemDesc:Int,
textFieldLabel:Int,
myDpm:DevicePolicyManager,
focusMgr:FocusManager,
allowZero:Boolean,
getMethod:()->String,
@@ -215,7 +226,7 @@ fun PasswordItem(
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(10.dp)
) {
var inputContent by remember{ mutableStateOf(getMethod()) }
var inputContent by remember{ mutableStateOf(if(isDeviceOwner(myDpm)){getMethod()}else{""}) }
var inputContentEdited by remember{ mutableStateOf(false) }
var ableToApply by remember{ mutableStateOf(true) }
Text(text = stringResource(itemName), style = MaterialTheme.typography.titleLarge)
@@ -240,29 +251,20 @@ fun PasswordItem(
}
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()})
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
enabled = isDeviceOwner(myDpm)
)
if(ableToApply){
Icon(
imageVector = Icons.Outlined.Check,
contentDescription = null,
tint = if(inputContentEdited){MaterialTheme.colorScheme.onError}else{MaterialTheme.colorScheme.onPrimary},
modifier = Modifier
.clip(RoundedCornerShape(20))
.background(
color = if (inputContentEdited) {
MaterialTheme.colorScheme.error
} else {
MaterialTheme.colorScheme.primary
}
)
.clickable(onClick = {
focusMgr.clearFocus()
setMethod(inputContent)
inputContentEdited = inputContent != getMethod()
})
.padding(8.dp)
IconButton(
onClick = { focusMgr.clearFocus() ; setMethod(inputContent) ; inputContentEdited=inputContent!=getMethod() },
enabled = isDeviceOwner(myDpm)&&ableToApply,
colors = IconButtonDefaults.iconButtonColors(
contentColor = if(inputContentEdited){MaterialTheme.colorScheme.onError}else{MaterialTheme.colorScheme.onPrimary},
containerColor = if(inputContentEdited){MaterialTheme.colorScheme.error}else{MaterialTheme.colorScheme.primary},
disabledContentColor = Color.Transparent,
disabledContainerColor = Color.Transparent
)
) {
Icon(imageVector = Icons.Outlined.Check, contentDescription = null)
}
}
}

View File

@@ -38,6 +38,7 @@ import androidx.core.content.ContextCompat.startActivity
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
@Composable
fun DpmPermissions(myDpm: DevicePolicyManager, myComponent: ComponentName, myContext:Context,navCtrl:NavHostController){
//da:DeviceAdmin do:DeviceOwner
@@ -53,7 +54,7 @@ fun DpmPermissions(myDpm: DevicePolicyManager, myComponent: ComponentName, myCon
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp)
.padding(vertical = 5.dp)
.clip(RoundedCornerShape(15))
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(10.dp),
@@ -78,15 +79,33 @@ fun DpmPermissions(myDpm: DevicePolicyManager, myComponent: ComponentName, myCon
Text("撤销")
}
}else{
Button(onClick = { ActivateDeviceAdmin(myComponent, myContext) }) {
/*Button(onClick = { activateDeviceAdmin(myContext,myComponent) }) {
Text("激活")
}*/
}
}
if(!isda){
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 5.dp)
.clip(RoundedCornerShape(15.dp))
.background(color = MaterialTheme.colorScheme.tertiaryContainer)
.padding(10.dp),
horizontalAlignment = Alignment.Start
) {
Text("你可以在adb shell中使用以下命令激活Device Admin")
SelectionContainer {
Text("dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver",
color = MaterialTheme.colorScheme.onTertiaryContainer)
}
Text("或者进入设置 -> 安全 -> 更多安全设置 -> 设备管理应用 -> Android Owner")
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp)
.padding(vertical = 5.dp)
.clip(RoundedCornerShape(15))
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(10.dp),
@@ -112,38 +131,59 @@ fun DpmPermissions(myDpm: DevicePolicyManager, myComponent: ComponentName, myCon
}
}
}
Column(
horizontalAlignment = Alignment.Start
) {
if(isdo||isda){
Text(
text = "注意!在这里撤销权限不会清除配置。比如:被停用的应用会保持停用状态",
color = MaterialTheme.colorScheme.onErrorContainer,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 5.dp)
.clip(RoundedCornerShape(15))
.background(color = MaterialTheme.colorScheme.errorContainer)
.padding(6.dp)
)
}
Spacer(Modifier.padding(5.dp))
if(!isda){
Text("你可以在adb shell中使用以下命令激活Device Admin")
SelectionContainer {
Text("dpm set-active-admin com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver")
if(isdo){
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 5.dp)
.clip(RoundedCornerShape(15))
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(8.dp)
) {
Text(text = "设备信息", style = MaterialTheme.typography.titleLarge)
if(VERSION.SDK_INT>=30){
val orgDevice = myDpm.isOrganizationOwnedDeviceWithManagedProfile
Text("由组织拥有的工作资料设备:$orgDevice")
}
if(VERSION.SDK_INT>=34&&(myDpm.isProfileOwnerApp("com.binbin.androidowner")||myDpm.isManagedProfile(myComponent))){
val financed = myDpm.isDeviceFinanced
Text("Financed Device : $financed")
}
}
Text("或者进入设置 -> 安全 -> 更多安全设置 -> 设备管理应用 -> Android Owner")
}
if(!isdo){
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 5.dp)
.clip(RoundedCornerShape(15.dp))
.background(color = MaterialTheme.colorScheme.tertiaryContainer)
.padding(10.dp),
horizontalAlignment = Alignment.Start
) {
Text("你可以在adb shell中使用以下命令激活Device Owner")
SelectionContainer {
Text("dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver")
Text(text = "dpm set-device-owner com.binbin.androidowner/com.binbin.androidowner.MyDeviceAdminReceiver",
color = MaterialTheme.colorScheme.onTertiaryContainer)
}
if(!isda){
Text("使用此命令也会激活Device Admin")
}
}
}
if(isdo&&VERSION.SDK_INT>=24){
DeviceOwnerInfo(R.string.owner_lockscr_info,R.string.place_holder,R.string.owner_lockscr_info,focusManager,myContext,
{myDpm.deviceOwnerLockScreenInfo},{content -> myDpm.setDeviceOwnerLockScreenInfo(myComponent,content)})
@@ -152,8 +192,6 @@ fun DpmPermissions(myDpm: DevicePolicyManager, myComponent: ComponentName, myCon
DeviceOwnerInfo(R.string.long_support_msg,R.string.long_support_msg_desc,R.string.message,focusManager,myContext,
{myDpm.getLongSupportMessage(myComponent)},{content -> myDpm.setLongSupportMessage(myComponent,content)})
}
DeviceOwnerInfo(R.string.profile_name,R.string.unknown_feature,R.string.profile_name,focusManager,myContext,
{null},{content -> myDpm.setProfileName(myComponent,content)})
Spacer(Modifier.padding(vertical = 20.dp))
}
}
@@ -171,7 +209,7 @@ fun DeviceOwnerInfo(
Column(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 10.dp)
.padding(vertical = 5.dp)
.clip(RoundedCornerShape(12.dp))
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(10.dp)
@@ -216,13 +254,10 @@ fun DeviceOwnerInfo(
}
}
fun ActivateDeviceAdmin(myComponent: ComponentName,myContext: Context){
fun activateDeviceAdmin(inputContext:Context,inputComponent:ComponentName){
val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, myComponent)
intent.putExtra(
DevicePolicyManager.EXTRA_ADD_EXPLANATION,
"在这里激活Android Owner"
)
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, inputComponent)
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "在这里激活Android Owner")
intent.setFlags(FLAG_ACTIVITY_NEW_TASK)
startActivity(myContext,intent,null)
startActivity(inputContext,intent,null)
}

View File

@@ -50,7 +50,7 @@ fun SysUpdatePolicy(myDpm:DevicePolicyManager,myComponent:ComponentName,myContex
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(8.dp)
) {
val sysUpdateInfo = myDpm.getPendingSystemUpdate(myComponent)
val sysUpdateInfo = if(isDeviceOwner(myDpm)){myDpm.getPendingSystemUpdate(myComponent)}else{null}
if(sysUpdateInfo!=null){
Text("Update first available: ${Date(sysUpdateInfo.receivedTime)}")
Text("Hash code: ${sysUpdateInfo.hashCode()}")
@@ -115,7 +115,7 @@ fun SysUpdatePolicy(myDpm:DevicePolicyManager,myComponent:ComponentName,myContex
}
Button(
onClick = {myDpm.setSystemUpdatePolicy(myComponent,policy);Toast.makeText(myContext, "成功!", Toast.LENGTH_SHORT).show()},
enabled = myDpm.isDeviceOwnerApp("com.binbin.androidowner")
enabled = isDeviceOwner(myDpm)
) {
Text("应用")
}

View File

@@ -1,46 +1,13 @@
package com.binbin.androidowner;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInstaller;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.os.UserHandle;
import android.os.UserManager;
import java.util.List;
public class Test {
public static void installPackage(Context context, InputStream inputStream)
throws IOException {
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
int sessionId = packageInstaller.createSession(new PackageInstaller
.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
long sizeBytes = 0;
OutputStream out;
out = session.openWrite("my_app_session", 0, sizeBytes);
int total = 0;
byte[] buffer = new byte[65536];
int c;
while ((c = inputStream.read(buffer)) != -1) {
total += c;
out.write(buffer, 0, c);
}
session.fsync(out);
inputStream.close();
out.close();
// fake intent
IntentSender statusReceiver = null;
Intent intent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
1337111117, intent, PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
session.close();
public static List<UserHandle> returnUsers(Context myContext){
UserManager userManager = (UserManager) myContext.getSystemService(Context.USER_SERVICE);
return userManager.getUserProfiles();
}
}

View File

@@ -2,31 +2,248 @@ package com.binbin.androidowner
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.os.Build
import android.content.Context
import android.os.Build.VERSION
import android.os.UserHandle
import android.os.UserManager
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.core.os.UserManagerCompat
@Composable
fun User(myDpm:DevicePolicyManager,myComponent:ComponentName){
Column {
Column {
Text(text = "参数", style = MaterialTheme.typography.titleLarge)
if(Build.VERSION.SDK_INT>=34&&(myDpm.isProfileOwnerApp("com.binbin.androidowner")||myDpm.isManagedProfile(myComponent))){
val financed = myDpm.isDeviceFinanced
Text("Financed Device : $financed")
fun UserManage(myDpm:DevicePolicyManager,myComponent:ComponentName,myContext: Context){
Column(
modifier = Modifier.verticalScroll(rememberScrollState())
) {
//val myUM = myContext.getSystemService(Context.USER_SERVICE)
val currentUser = android.os.Process.myUserHandle()
val userList = Test.returnUsers(myContext)
Text("因为我的模拟器的多用户无法使用,部分功能并未测试")
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp)
.clip(RoundedCornerShape(16.dp))
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(10.dp)
) {
Text(text = "用户信息", style = MaterialTheme.typography.titleLarge)
Text("用户个数:${userList.size}")
Spacer(Modifier.padding(vertical = 5.dp))
Text("用户已解锁:${UserManagerCompat.isUserUnlocked(myContext)}")
if(VERSION.SDK_INT>=24){
Text("支持多用户:${UserManager.supportsMultipleUsers()}")
}
if (Build.VERSION.SDK_INT >= 28) {
if(VERSION.SDK_INT>=31){
Text("Headless system user: ${UserManager.isHeadlessSystemUserMode()}")
}
Spacer(Modifier.padding(vertical = 5.dp))
if (VERSION.SDK_INT >= 28&& isDeviceOwner(myDpm)) {
val logoutable = myDpm.isLogoutEnabled
Text(text = "用户可以退出 : $logoutable")
val ephemeralUser = myDpm.isEphemeralUser(myComponent)
Text(text = "临时用户: $ephemeralUser")
Text("切换用户后或设备重启后会删除临时用户")
val affiliatedUser = myDpm.isAffiliatedUser
Text(text = "Affiliated User:$affiliatedUser")
Text(text = "Affiliated User: $affiliatedUser")
}
Spacer(Modifier.padding(5.dp))
Text("切换用户后或设备重启后会删除临时用户")
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp)
.clip(RoundedCornerShape(14.dp))
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(10.dp)
) {
Text(text = "用户操作", style = MaterialTheme.typography.titleLarge)
if(VERSION.SDK_INT>28){
var resultForLogout by remember{ mutableIntStateOf(-1) }
var resultForStop by remember{ mutableIntStateOf(-1) }
Text("登出用户需要成为次级用户的Profile Owner")
Button(onClick = {resultForLogout = myDpm.logoutUser(myComponent)}, enabled = isDeviceOwner(myDpm)) {
Text("登出用户")
}
if(resultForLogout!=-1){
Text(userOperationResultCode(resultForLogout))
}
Button(onClick = {resultForStop = myDpm.stopUser(myComponent,currentUser)}, enabled = isDeviceOwner(myDpm)) {
Text("停止用户")
}
if(resultForStop!=-1){
Text(userOperationResultCode(resultForStop))
}
}
Button(
onClick = {
val success = myDpm.removeUser(myComponent,currentUser)
if(success){
Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show()
}
},
enabled = isDeviceOwner(myDpm)
) {
Text("移除用户")
}
}
if(VERSION.SDK_INT>=24){
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp)
.clip(RoundedCornerShape(14.dp))
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(10.dp)
) {
var userName by remember{ mutableStateOf("") }
Text(text = "创建用户", style = MaterialTheme.typography.titleLarge)
TextField(
value = userName,
onValueChange = {userName=it},
label = {Text("用户名")},
modifier = Modifier.padding(vertical = 4.dp),
enabled = isDeviceOwner(myDpm)
)
var selectedFlag by remember{ mutableIntStateOf(0) }
RadioButtonItem("",{selectedFlag==0},{selectedFlag=0})
RadioButtonItem("跳过创建用户向导",{selectedFlag==DevicePolicyManager.SKIP_SETUP_WIZARD},{selectedFlag=DevicePolicyManager.SKIP_SETUP_WIZARD})
if(VERSION.SDK_INT>=28){
RadioButtonItem("临时用户",{selectedFlag==DevicePolicyManager.MAKE_USER_EPHEMERAL},{selectedFlag=DevicePolicyManager.MAKE_USER_EPHEMERAL})
RadioButtonItem("启用所有系统应用",{selectedFlag==DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED},{selectedFlag=DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED})
}
var newUserHandle: UserHandle? by remember{ mutableStateOf(null) }
Row {
Button(
onClick = {newUserHandle=myDpm.createAndManageUser(myComponent,userName,myComponent,null,selectedFlag)},
enabled = isDeviceOwner(myDpm)
) {
Text("创建")
}
if(newUserHandle!=null){
Spacer(Modifier.padding(horizontal = 4.dp))
Button(
onClick = {
if(myDpm.switchUser(myComponent,newUserHandle)){
Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()
} else{
Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show()
}
}
) {
Text("切换至新用户")
}
}
}
}
}else{
Text("创建用户需安卓7")
}
UserSessionMessage("用户名","用户名",myDpm,myContext,{null},{msg -> myDpm.setProfileName(myComponent, msg.toString())})
if(VERSION.SDK_INT>=28){
UserSessionMessage("用户会话开始消息","消息",myDpm,myContext,{myDpm.getStartUserSessionMessage(myComponent)},{msg -> myDpm.setStartUserSessionMessage(myComponent,msg)})
UserSessionMessage("用户会话结束消息","消息",myDpm,myContext,{myDpm.getEndUserSessionMessage(myComponent)},{msg -> myDpm.setEndUserSessionMessage(myComponent,msg)})
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
fun UserSessionMessage(
text:String,
textField:String,
myDpm:DevicePolicyManager,
myContext: Context,
get:()->CharSequence?,
setMsg:(msg:CharSequence?)->Unit
){
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp)
.clip(RoundedCornerShape(10))
.background(color = MaterialTheme.colorScheme.primaryContainer)
.padding(10.dp)
) {
val focusMgr = LocalFocusManager.current
var msg:CharSequence? by remember{ mutableStateOf(null) }
if(isDeviceOwner(myDpm)){
msg = if(get()==null){""}else{get()}
}
Text(text = text, style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.onPrimaryContainer)
TextField(
value = msg.toString(),
onValueChange = {msg=it},
label = {Text(textField)},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()}),
modifier = Modifier.padding(vertical = 6.dp),
enabled = isDeviceOwner(myDpm)
)
Row {
Button(
onClick = {
focusMgr.clearFocus()
setMsg(msg)
msg = if(get()==null){""}else{get()}
Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()
},
enabled = isDeviceOwner(myDpm)
) {
Text("应用")
}
Spacer(Modifier.padding(horizontal = 5.dp))
Button(
onClick = {
focusMgr.clearFocus()
setMsg(null)
msg = if(get()==null){""}else{get()}
Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()
},
enabled = isDeviceOwner(myDpm)
) {
Text("使用默认")
}
}
}
}
fun userOperationResultCode(result:Int): String {
return when(result){
UserManager.USER_OPERATION_SUCCESS->"USER_OPERATION_SUCCESS"
UserManager.USER_OPERATION_ERROR_UNKNOWN->"USER_OPERATION_ERROR_UNKNOWN"
UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE->"USER_OPERATION_ERROR_MANAGED_PROFILE"
UserManager.USER_OPERATION_ERROR_CURRENT_USER->"USER_OPERATION_ERROR_CURRENT_USER"
else->"Unknown"
}
}

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
@@ -21,6 +22,7 @@ import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -35,7 +37,7 @@ import androidx.compose.ui.unit.dp
@Composable
fun UserRestriction(myDpm: DevicePolicyManager, myComponent: ComponentName){
val verticalScrolling = rememberScrollState()
var currentSection by remember{ mutableStateOf(0) }
var currentSection by remember{ mutableIntStateOf(0) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
@@ -171,6 +173,7 @@ fun UserRestriction(myDpm: DevicePolicyManager, myComponent: ComponentName){
if(VERSION.SDK_INT<34){
Text("以下功能需要安卓14或以上2G信号、启用设备管理器、超宽频段无线电")
}
Spacer(Modifier.padding(vertical = 20.dp))
}
}

View File

@@ -119,11 +119,13 @@
<string name="support_msg">提供支持的短消息</string>
<string name="support_msg_desc">如果你禁用了某个功能,用户尝试使用这个功能时会看见这个消息(可多行)</string>
<string name="message">消息</string>
<string name="profile_name">预设</string>
<string name="profile_name">用户</string>
<string name="long_support_msg">提供支持的长消息</string>
<string name="long_support_msg_desc">都是显示短消息,长消息不知道在哪里显示</string>
<string name="disable_bt_contact_share">禁止蓝牙分享通讯录</string>
<string name="sys_update_policy">系统更新策略</string>
<string name="sys_update_policy_desc">管理系统更新策略</string>
<string name="disable_keyguard">禁止锁屏(需无密码)</string>
<string name="user_manage">用户管理</string>
<string name="user_manage_desc">查看用户状态,添加用户</string>
</resources>