fix user operation problems

This commit is contained in:
BinTianqi
2024-02-01 15:34:08 +08:00
parent ca46a775c8
commit 71bf3842b9
7 changed files with 197 additions and 65 deletions

View File

@@ -31,6 +31,10 @@
- 显示与音量:禁止调整亮度、音量 - 显示与音量:禁止调整亮度、音量
- 用户和工作资料:禁止添加/切换/移除用户,禁止添加/移除工作资料 - 用户和工作资料:禁止添加/切换/移除用户,禁止添加/移除工作资料
- 杂项:禁止自动填充服务、禁止调试 - 杂项:禁止自动填充服务、禁止调试
- 用户管理
- 添加用户并切换至新用户
- 修改当前用户的名称
- 设置切换用户时的提示
- 密码 - 密码
- 修改密码 - 修改密码
- 最大密码错误次数 - 最大密码错误次数

View File

@@ -52,16 +52,16 @@ android {
dependencies { dependencies {
implementation("androidx.core:core-ktx:1.10.1") implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.activity:activity-compose:1.7.0") implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2023.08.00")) implementation(platform("androidx.compose:compose-bom:2023.08.00"))
implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3:1.1.1") implementation("androidx.compose.material3:material3:1.1.2")
implementation("androidx.navigation:navigation-compose:2.7.6") implementation("androidx.navigation:navigation-compose:2.7.6")
implementation("com.google.accompanist:accompanist-systemuicontroller:0.33.2-alpha") implementation("com.google.accompanist:accompanist-systemuicontroller:0.34.0")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

View File

@@ -160,7 +160,7 @@ fun MyScaffold(){
composable(route = "Permissions", content = { DpmPermissions(navCtrl)}) composable(route = "Permissions", content = { DpmPermissions(navCtrl)})
composable(route = "ApplicationManage", content = { ApplicationManage()}) composable(route = "ApplicationManage", content = { ApplicationManage()})
composable(route = "UserRestriction", content = { UserRestriction()}) composable(route = "UserRestriction", content = { UserRestriction()})
composable(route = "UserManage", content = { UserManage()}) composable(route = "UserManage", content = { UserManage(navCtrl)})
composable(route = "Password", content = { Password()}) composable(route = "Password", content = { Password()})
composable(route = "AppSetting", content = { AppSetting(navCtrl)}) composable(route = "AppSetting", content = { AppSetting(navCtrl)})
} }

View File

@@ -11,10 +11,10 @@ class MyDeviceAdminReceiver : DeviceAdminReceiver() {
super.onEnabled(context, intent) super.onEnabled(context, intent)
Toast.makeText(context, "已启用", Toast.LENGTH_SHORT).show() Toast.makeText(context, "已启用", Toast.LENGTH_SHORT).show()
} }
override fun onReceive(context: Context, intent: Intent) { /*override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent) super.onReceive(context, intent)
Toast.makeText(context, "已接收", Toast.LENGTH_SHORT).show() Toast.makeText(context, "已接收", Toast.LENGTH_SHORT).show()
} }*/
override fun onDisableRequested(context: Context, intent: Intent): CharSequence { override fun onDisableRequested(context: Context, intent: Intent): CharSequence {
Toast.makeText(context, "撤销授权", Toast.LENGTH_SHORT).show() Toast.makeText(context, "撤销授权", Toast.LENGTH_SHORT).show()
return "这是取消时的提示" return "这是取消时的提示"

View File

@@ -1,13 +0,0 @@
package com.binbin.androidowner;
import android.content.Context;
import android.os.UserHandle;
import android.os.UserManager;
import java.util.List;
public class Test {
public static List<UserHandle> returnUsers(Context myContext){
UserManager userManager = (UserManager) myContext.getSystemService(Context.USER_SERVICE);
return userManager.getUserProfiles();
}
}

View File

@@ -9,6 +9,7 @@ import android.os.UserHandle
import android.os.UserManager import android.os.UserManager
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -17,10 +18,12 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
@@ -30,32 +33,33 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.os.UserManagerCompat import androidx.core.os.UserManagerCompat
import androidx.navigation.NavHostController
@Composable @Composable
fun UserManage(){ fun UserManage(navCtrl:NavHostController){
Column( Column(
modifier = Modifier.verticalScroll(rememberScrollState()) modifier = Modifier.verticalScroll(rememberScrollState())
) { ) {
//val myUM = myContext.getSystemService(Context.USER_SERVICE)
val myContext = LocalContext.current val myContext = LocalContext.current
val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java)
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
val currentUser = android.os.Process.myUserHandle() val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager
val userList = Test.returnUsers(myContext)
val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE)
val isWear = sharedPref.getBoolean("isWear",false) val isWear = sharedPref.getBoolean("isWear",false)
Column(modifier = sections()) { Column(modifier = sections()) {
Text(text = "用户信息", style = MaterialTheme.typography.titleLarge,color = MaterialTheme.colorScheme.onPrimaryContainer) Text(text = "用户信息", style = MaterialTheme.typography.titleLarge,color = MaterialTheme.colorScheme.onPrimaryContainer)
Text("用户个数:${userList.size}",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge})
Spacer(Modifier.padding(vertical = if(isWear){2.dp}else{5.dp}))
Text("用户已解锁:${UserManagerCompat.isUserUnlocked(myContext)}",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge}) Text("用户已解锁:${UserManagerCompat.isUserUnlocked(myContext)}",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge})
if(VERSION.SDK_INT>=24){ if(VERSION.SDK_INT>=24){
Text("支持多用户:${UserManager.supportsMultipleUsers()}",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge}) Text("支持多用户:${UserManager.supportsMultipleUsers()}",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge})
@@ -75,49 +79,162 @@ fun UserManage(){
Text(text = "次级用户: $affiliatedUser",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge}) Text(text = "次级用户: $affiliatedUser",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge})
} }
Spacer(Modifier.padding(vertical = if(isWear){2.dp}else{5.dp})) Spacer(Modifier.padding(vertical = if(isWear){2.dp}else{5.dp}))
Text("切换用户后或设备重启后会删除临时用户",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge}) Text("当前UID${android.os.Process.myUid()}")
Text("当前UserID${getCurrentUserId()}")
Text("当前用户序列号:${userManager.getSerialNumberForUser(android.os.Process.myUserHandle())}")
} }
Column(modifier = sections()) { Column(modifier = sections()) {
Text(text = "用户操作", style = MaterialTheme.typography.titleLarge,color = MaterialTheme.colorScheme.onPrimaryContainer) Text(text = "用户操作", style = MaterialTheme.typography.titleLarge,color = MaterialTheme.colorScheme.onPrimaryContainer)
var idInput by remember{ mutableStateOf("") }
var userHandleById:UserHandle by remember{ mutableStateOf(android.os.Process.myUserHandle()) }
var useUid by remember{ mutableStateOf(false) }
TextField(
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 ?: android.os.Process.myUserHandle()
}
},
label = {Text(if(useUid){"UID"}else{"序列号"})},
enabled = isDeviceOwner(myDpm),
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus()})
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clip(RoundedCornerShape(15.dp))
.clickable(enabled = VERSION.SDK_INT>=24&&isDeviceOwner(myDpm)){useUid=!useUid}
.padding(end = 12.dp)
){
Checkbox(
checked = useUid,
onCheckedChange = {useUid=it},
enabled = VERSION.SDK_INT>=24&& isDeviceOwner(myDpm)
)
Text(text = "使用UID(不靠谱)",modifier = Modifier.padding(bottom = 2.dp))
}
if(VERSION.SDK_INT>28){ if(VERSION.SDK_INT>28){
var resultForLogout by remember{ mutableIntStateOf(-1) } if(isProfileOwner(myDpm)&&myDpm.isAffiliatedUser){
var resultForStop by remember{ mutableIntStateOf(-1) } Button(
Text("登出用户需要成为次级用户的Profile Owner",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge}) onClick = {
Button(onClick = {resultForLogout = myDpm.logoutUser(myComponent)}, enabled = isProfileOwner(myDpm)) { val result = myDpm.logoutUser(myComponent)
Text("登出用户") Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show()
},
enabled = isProfileOwner(myDpm),
modifier = Modifier.fillMaxWidth()
) {
Text("登出当前用户")
}
} }
if(resultForLogout!=-1){ Button(
Text(userOperationResultCode(resultForLogout)) onClick = {
} focusMgr.clearFocus()
Button(onClick = {myDpm.switchUser(myComponent,currentUser)}, enabled = isDeviceOwner(myDpm)) { val result = myDpm.startUserInBackground(myComponent,userHandleById)
Text("切换用户") Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show()
} },
Button(onClick = {resultForStop = myDpm.stopUser(myComponent,currentUser)}, enabled = isDeviceOwner(myDpm)) { enabled = isDeviceOwner(myDpm),
Text("停止用户") modifier = Modifier.fillMaxWidth()
} ){
if(resultForStop!=-1){ Text("在后台启动用户")
Text(userOperationResultCode(resultForStop))
}
Button(onClick = {myDpm.setProfileEnabled(myComponent)}, enabled = isProfileOwner(myDpm)||isDeviceOwner(myDpm)) {
Text(text = "启用资料")
} }
} }
Button( Button(
onClick = { onClick = {
val success = myDpm.removeUser(myComponent,currentUser) focusMgr.clearFocus()
if(success){ if(myDpm.switchUser(myComponent,userHandleById)){
focusMgr.clearFocus()
Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()
}else{ }else{
Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show()
} }
}, },
enabled = isDeviceOwner(myDpm) enabled = isDeviceOwner(myDpm),
modifier = Modifier.fillMaxWidth()
) { ) {
Text("移除用户") Text("切换至用户")
} }
Button(onClick = { createWorkProfile(myContext)}) { Row(
Text("创建工作资料") modifier = if(isWear){
Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())}else{Modifier.fillMaxWidth()},
horizontalArrangement = Arrangement.SpaceBetween
){
Button(
onClick = {
focusMgr.clearFocus()
try{
if(VERSION.SDK_INT>=28){
val result = myDpm.stopUser(myComponent,userHandleById)
Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show()
}
}catch(e:IllegalArgumentException){
Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show()
}
},
enabled = isDeviceOwner(myDpm)&&VERSION.SDK_INT>=28,
modifier = Modifier.fillMaxWidth(0.48F)
) {
Text("停止用户")
}
Button(
onClick = {
focusMgr.clearFocus()
if(myDpm.removeUser(myComponent,userHandleById)){
Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()
idInput=""
}else{
Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show()
}
},
enabled = isDeviceOwner(myDpm),
modifier = Modifier.fillMaxWidth(0.92F)
) {
Text("移除用户")
}
}
if(VERSION.SDK_INT<28){
Text("停止用户需API28")
}
}
Column(modifier = sections()) {
Text(text = "工作资料", style = MaterialTheme.typography.titleLarge)
Row(
modifier = if(isWear){
Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())}else{Modifier.fillMaxWidth()},
horizontalArrangement = Arrangement.SpaceBetween
){
Button(
onClick = { createWorkProfile(myContext)},
modifier = Modifier.fillMaxWidth(0.48F)
) {
Text("创建")
}
Button(
onClick = {
try{
myDpm.setProfileEnabled(myComponent)
}catch(e:SecurityException){
Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show()
}
},
enabled = isProfileOwner(myDpm)||isDeviceOwner(myDpm),
modifier = Modifier.fillMaxWidth(0.95F)
) {
Text(text = "启用")
}
} }
Text("可能无法创建工作资料",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge}) Text("可能无法创建工作资料",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge})
} }
@@ -143,12 +260,23 @@ fun UserManage(){
RadioButtonItem("启用所有系统应用",{selectedFlag==DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED},{selectedFlag=DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED}) RadioButtonItem("启用所有系统应用",{selectedFlag==DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED},{selectedFlag=DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED})
} }
var newUserHandle: UserHandle? by remember{ mutableStateOf(null) } var newUserHandle: UserHandle? by remember{ mutableStateOf(null) }
Row(modifier = if(isWear){Modifier.horizontalScroll(rememberScrollState())}else{Modifier}) { Row(
modifier = if(isWear){Modifier.fillMaxWidth().horizontalScroll(rememberScrollState())}else{Modifier.fillMaxWidth()},
horizontalArrangement = Arrangement.SpaceBetween
) {
Button( Button(
onClick = {newUserHandle=myDpm.createAndManageUser(myComponent,userName,myComponent,null,selectedFlag);focusMgr.clearFocus()}, onClick = {
newUserHandle=myDpm.createAndManageUser(myComponent,userName,myComponent,null,selectedFlag)
focusMgr.clearFocus()
if(newUserHandle!=null){
Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show()
}
},
enabled = isDeviceOwner(myDpm), enabled = isDeviceOwner(myDpm),
modifier = if(!isWear){ modifier = if(!isWear){
if(newUserHandle==null){Modifier.fillMaxWidth(1F)}else{Modifier.fillMaxWidth(0.48F)} if(newUserHandle==null){Modifier.fillMaxWidth(1F)}else{Modifier.fillMaxWidth(0.4F)}
}else{Modifier} }else{Modifier}
) { ) {
Text("创建") Text("创建")
@@ -159,17 +287,20 @@ fun UserManage(){
onClick = { onClick = {
if(myDpm.switchUser(myComponent,newUserHandle)){ if(myDpm.switchUser(myComponent,newUserHandle)){
Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()
navCtrl.navigate("HomePage")
} else{ } else{
Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show()
} }
}, },
modifier = if(isWear){Modifier}else{Modifier.fillMaxWidth(0.92F)} modifier = if(isWear){Modifier}else{Modifier.fillMaxWidth(0.97F)}
) { ) {
Text("切换至新用户") Text("切换至新用户")
} }
} }
} }
if(newUserHandle!=null){
Text("新用户的序列号:${userManager.getSerialNumberForUser(newUserHandle)}")
}
} }
}else{ }else{
Text("创建用户需安卓7") Text("创建用户需安卓7")
@@ -243,11 +374,11 @@ fun UserSessionMessage(
fun userOperationResultCode(result:Int): String { fun userOperationResultCode(result:Int): String {
return when(result){ return when(result){
UserManager.USER_OPERATION_SUCCESS->"USER_OPERATION_SUCCESS" UserManager.USER_OPERATION_SUCCESS->"成功"
UserManager.USER_OPERATION_ERROR_UNKNOWN->"USER_OPERATION_ERROR_UNKNOWN" UserManager.USER_OPERATION_ERROR_UNKNOWN->"未知结果(失败)"
UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE->"USER_OPERATION_ERROR_MANAGED_PROFILE" UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE->"失败:受管理的资料"
UserManager.USER_OPERATION_ERROR_CURRENT_USER->"USER_OPERATION_ERROR_CURRENT_USER" UserManager.USER_OPERATION_ERROR_CURRENT_USER->"失败:当前用户"
else->"Unknown" else->"未知"
} }
} }
@@ -261,3 +392,13 @@ private fun createWorkProfile(myContext: Context) {
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,"hello") intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,"hello")
myContext.startActivity(intent) myContext.startActivity(intent)
} }
fun getCurrentUserId(): Int {
try {
val uh = UserHandle::class.java.getDeclaredMethod("myUserId")
uh.isAccessible = true
val userId = uh.invoke(null)
if (userId is Int) { return userId }
} catch (ignored: Exception) { }
return -1
}

View File

@@ -71,7 +71,7 @@ fun UserRestriction(){
color = MaterialTheme.colorScheme.error) color = MaterialTheme.colorScheme.error)
} }
if(isProfileOwner(myDpm)){ if(isProfileOwner(myDpm)){
Text(text = "Profile owner无法更改部分功能", Text(text = "Profile owner无法使用部分功能",
style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}) style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium})
} }
if(isWear){ if(isWear){
@@ -221,7 +221,7 @@ private fun UserRestrictionItem(
} }
}catch(e:SecurityException){ }catch(e:SecurityException){
if(isProfileOwner(myDpm)){ if(isProfileOwner(myDpm)){
Toast.makeText(myContext, "Profile owner 无法更改", Toast.LENGTH_SHORT).show() Toast.makeText(myContext, "需要DeviceOwner", Toast.LENGTH_SHORT).show()
} }
} }
strictState = myDpm.getUserRestrictions(myComponent).getBoolean(restriction) strictState = myDpm.getUserRestrictions(myComponent).getBoolean(restriction)