diff --git a/Readme.md b/Readme.md index 71866e6..3bb45dc 100644 --- a/Readme.md +++ b/Readme.md @@ -31,6 +31,10 @@ - 显示与音量:禁止调整亮度、音量 - 用户和工作资料:禁止添加/切换/移除用户,禁止添加/移除工作资料 - 杂项:禁止自动填充服务、禁止调试 +- 用户管理 + - 添加用户并切换至新用户 + - 修改当前用户的名称 + - 设置切换用户时的提示 - 密码 - 修改密码 - 最大密码错误次数 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 70554a6..67ebe2b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -52,16 +52,16 @@ android { dependencies { - implementation("androidx.core:core-ktx:1.10.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") - implementation("androidx.activity:activity-compose:1.7.0") + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") + implementation("androidx.activity:activity-compose:1.8.2") implementation(platform("androidx.compose:compose-bom:2023.08.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") 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("com.google.accompanist:accompanist-systemuicontroller:0.33.2-alpha") + implementation("com.google.accompanist:accompanist-systemuicontroller:0.34.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") diff --git a/app/src/main/java/com/binbin/androidowner/MainActivity.kt b/app/src/main/java/com/binbin/androidowner/MainActivity.kt index 6d11504..1453b1a 100644 --- a/app/src/main/java/com/binbin/androidowner/MainActivity.kt +++ b/app/src/main/java/com/binbin/androidowner/MainActivity.kt @@ -160,7 +160,7 @@ fun MyScaffold(){ composable(route = "Permissions", content = { DpmPermissions(navCtrl)}) composable(route = "ApplicationManage", content = { ApplicationManage()}) composable(route = "UserRestriction", content = { UserRestriction()}) - composable(route = "UserManage", content = { UserManage()}) + composable(route = "UserManage", content = { UserManage(navCtrl)}) composable(route = "Password", content = { Password()}) composable(route = "AppSetting", content = { AppSetting(navCtrl)}) } diff --git a/app/src/main/java/com/binbin/androidowner/Receiver.kt b/app/src/main/java/com/binbin/androidowner/Receiver.kt index caa2d72..43efff7 100644 --- a/app/src/main/java/com/binbin/androidowner/Receiver.kt +++ b/app/src/main/java/com/binbin/androidowner/Receiver.kt @@ -11,10 +11,10 @@ class MyDeviceAdminReceiver : DeviceAdminReceiver() { super.onEnabled(context, intent) 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) Toast.makeText(context, "已接收", Toast.LENGTH_SHORT).show() - } + }*/ override fun onDisableRequested(context: Context, intent: Intent): CharSequence { Toast.makeText(context, "撤销授权", Toast.LENGTH_SHORT).show() return "这是取消时的提示" diff --git a/app/src/main/java/com/binbin/androidowner/Test.java b/app/src/main/java/com/binbin/androidowner/Test.java deleted file mode 100644 index e90bf88..0000000 --- a/app/src/main/java/com/binbin/androidowner/Test.java +++ /dev/null @@ -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 returnUsers(Context myContext){ - UserManager userManager = (UserManager) myContext.getSystemService(Context.USER_SERVICE); - return userManager.getUserProfiles(); - } -} diff --git a/app/src/main/java/com/binbin/androidowner/User.kt b/app/src/main/java/com/binbin/androidowner/User.kt index be53071..fb1920a 100644 --- a/app/src/main/java/com/binbin/androidowner/User.kt +++ b/app/src/main/java/com/binbin/androidowner/User.kt @@ -9,6 +9,7 @@ import android.os.UserHandle import android.os.UserManager import android.widget.Toast import androidx.activity.ComponentActivity +import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement 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.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.Checkbox import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextField @@ -30,32 +33,33 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment 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.LocalFocusManager 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 @Composable -fun UserManage(){ +fun UserManage(navCtrl:NavHostController){ Column( modifier = Modifier.verticalScroll(rememberScrollState()) ) { - //val myUM = myContext.getSystemService(Context.USER_SERVICE) val myContext = LocalContext.current val myDpm = myContext.getSystemService(ComponentActivity.DEVICE_POLICY_SERVICE) as DevicePolicyManager val myComponent = ComponentName(myContext,MyDeviceAdminReceiver::class.java) val focusMgr = LocalFocusManager.current - val currentUser = android.os.Process.myUserHandle() - val userList = Test.returnUsers(myContext) + val userManager = myContext.getSystemService(Context.USER_SERVICE) as UserManager val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) val isWear = sharedPref.getBoolean("isWear",false) Column(modifier = sections()) { 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}) if(VERSION.SDK_INT>=24){ 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}) } 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()) { 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){ - var resultForLogout by remember{ mutableIntStateOf(-1) } - var resultForStop by remember{ mutableIntStateOf(-1) } - Text("登出用户需要成为次级用户的Profile Owner",style = if(isWear){MaterialTheme.typography.bodyMedium}else{MaterialTheme.typography.bodyLarge}) - Button(onClick = {resultForLogout = myDpm.logoutUser(myComponent)}, enabled = isProfileOwner(myDpm)) { - Text("登出用户") + if(isProfileOwner(myDpm)&&myDpm.isAffiliatedUser){ + Button( + onClick = { + val result = myDpm.logoutUser(myComponent) + Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show() + }, + enabled = isProfileOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ) { + Text("登出当前用户") + } } - if(resultForLogout!=-1){ - Text(userOperationResultCode(resultForLogout)) - } - Button(onClick = {myDpm.switchUser(myComponent,currentUser)}, enabled = isDeviceOwner(myDpm)) { - Text("切换用户") - } - Button(onClick = {resultForStop = myDpm.stopUser(myComponent,currentUser)}, enabled = isDeviceOwner(myDpm)) { - Text("停止用户") - } - if(resultForStop!=-1){ - Text(userOperationResultCode(resultForStop)) - } - Button(onClick = {myDpm.setProfileEnabled(myComponent)}, enabled = isProfileOwner(myDpm)||isDeviceOwner(myDpm)) { - Text(text = "启用资料") + Button( + onClick = { + focusMgr.clearFocus() + val result = myDpm.startUserInBackground(myComponent,userHandleById) + Toast.makeText(myContext, userOperationResultCode(result), Toast.LENGTH_SHORT).show() + }, + enabled = isDeviceOwner(myDpm), + modifier = Modifier.fillMaxWidth() + ){ + Text("在后台启动用户") } } Button( onClick = { - val success = myDpm.removeUser(myComponent,currentUser) - if(success){ + focusMgr.clearFocus() + if(myDpm.switchUser(myComponent,userHandleById)){ + focusMgr.clearFocus() Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() }else{ Toast.makeText(myContext, "失败", Toast.LENGTH_SHORT).show() } }, - enabled = isDeviceOwner(myDpm) + enabled = isDeviceOwner(myDpm), + modifier = Modifier.fillMaxWidth() ) { - Text("移除用户") + Text("切换至用户") } - Button(onClick = { createWorkProfile(myContext)}) { - Text("创建工作资料") + Row( + 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}) } @@ -143,12 +260,23 @@ fun UserManage(){ RadioButtonItem("启用所有系统应用",{selectedFlag==DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED},{selectedFlag=DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED}) } 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( - 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), 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} ) { Text("创建") @@ -159,17 +287,20 @@ fun UserManage(){ onClick = { if(myDpm.switchUser(myComponent,newUserHandle)){ Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() + navCtrl.navigate("HomePage") } else{ 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("切换至新用户") } } } - + if(newUserHandle!=null){ + Text("新用户的序列号:${userManager.getSerialNumberForUser(newUserHandle)}") + } } }else{ Text("创建用户需安卓7") @@ -243,11 +374,11 @@ fun UserSessionMessage( 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" + UserManager.USER_OPERATION_SUCCESS->"成功" + UserManager.USER_OPERATION_ERROR_UNKNOWN->"未知结果(失败)" + UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE->"失败:受管理的资料" + UserManager.USER_OPERATION_ERROR_CURRENT_USER->"失败:当前用户" + else->"未知" } } @@ -261,3 +392,13 @@ private fun createWorkProfile(myContext: Context) { intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,"hello") 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 +} diff --git a/app/src/main/java/com/binbin/androidowner/UserRestrict.kt b/app/src/main/java/com/binbin/androidowner/UserRestrict.kt index 94960bc..9ab9af5 100644 --- a/app/src/main/java/com/binbin/androidowner/UserRestrict.kt +++ b/app/src/main/java/com/binbin/androidowner/UserRestrict.kt @@ -71,7 +71,7 @@ fun UserRestriction(){ color = MaterialTheme.colorScheme.error) } if(isProfileOwner(myDpm)){ - Text(text = "Profile owner无法更改部分功能", + Text(text = "Profile owner无法使用部分功能", style = if(!isWear){typography.bodyLarge}else{typography.bodyMedium}) } if(isWear){ @@ -221,7 +221,7 @@ private fun UserRestrictionItem( } }catch(e:SecurityException){ 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)