From 01c0a119a697488cd60140dd415bf5d85bd12b02 Mon Sep 17 00:00:00 2001 From: BinTianqi <1220958406@qq.com> Date: Sun, 21 Jan 2024 20:13:44 +0800 Subject: [PATCH] add user manage section --- Readme.md | 56 ++-- app/build.gradle.kts | 4 +- .../binbin/androidowner/ApplicationManage.kt | 57 ++++- .../com/binbin/androidowner/DeviceControl.kt | 62 ++++- .../com/binbin/androidowner/MainActivity.kt | 9 +- .../java/com/binbin/androidowner/Password.kt | 66 ++--- .../com/binbin/androidowner/Permissions.kt | 105 +++++--- .../binbin/androidowner/SystemUpdatePolicy.kt | 4 +- .../java/com/binbin/androidowner/Test.java | 45 +--- .../main/java/com/binbin/androidowner/User.kt | 239 +++++++++++++++++- .../com/binbin/androidowner/UserRestrict.kt | 5 +- app/src/main/res/values/strings.xml | 4 +- 12 files changed, 492 insertions(+), 164 deletions(-) diff --git a/Readme.md b/Readme.md index 135076e..9228e9d 100644 --- a/Readme.md +++ b/Readme.md @@ -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,工作资料和多用户相关 +- 安装/卸载应用,清除应用的缓存和存储空间 +- 应用管理的包选择器(目前只能手动输入包名) +- 适配手表 \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2a62567..f237136 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 { diff --git a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt b/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt index 58c26a6..fb885b5 100644 --- a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt +++ b/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt @@ -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 +} diff --git a/app/src/main/java/com/binbin/androidowner/DeviceControl.kt b/app/src/main/java/com/binbin/androidowner/DeviceControl.kt index f8986cb..11f0550 100644 --- a/app/src/main/java/com/binbin/androidowner/DeviceControl.kt +++ b/app/src/main/java/com/binbin/androidowner/DeviceControl.kt @@ -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("启用锁屏") } } - if(VERSION.SDK_INT>=24){ - Button(onClick = {myDpm.reboot(myComponent)}) { - 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)}, 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) ) } } diff --git a/app/src/main/java/com/binbin/androidowner/MainActivity.kt b/app/src/main/java/com/binbin/androidowner/MainActivity.kt index 2d41993..b09720d 100644 --- a/app/src/main/java/com/binbin/androidowner/MainActivity.kt +++ b/app/src/main/java/com/binbin/androidowner/MainActivity.kt @@ -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") +} diff --git a/app/src/main/java/com/binbin/androidowner/Password.kt b/app/src/main/java/com/binbin/androidowner/Password.kt index 510bee6..8b0c738 100644 --- a/app/src/main/java/com/binbin/androidowner/Password.kt +++ b/app/src/main/java/com/binbin/androidowner/Password.kt @@ -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) } } } diff --git a/app/src/main/java/com/binbin/androidowner/Permissions.kt b/app/src/main/java/com/binbin/androidowner/Permissions.kt index 0c4f178..e1932b2 100644 --- a/app/src/main/java/com/binbin/androidowner/Permissions.kt +++ b/app/src/main/java/com/binbin/androidowner/Permissions.kt @@ -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() - .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||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) + ) + } + 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){ + } + 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) } diff --git a/app/src/main/java/com/binbin/androidowner/SystemUpdatePolicy.kt b/app/src/main/java/com/binbin/androidowner/SystemUpdatePolicy.kt index 50a9222..2d3ed2c 100644 --- a/app/src/main/java/com/binbin/androidowner/SystemUpdatePolicy.kt +++ b/app/src/main/java/com/binbin/androidowner/SystemUpdatePolicy.kt @@ -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("应用") } diff --git a/app/src/main/java/com/binbin/androidowner/Test.java b/app/src/main/java/com/binbin/androidowner/Test.java index 75df460..e90bf88 100644 --- a/app/src/main/java/com/binbin/androidowner/Test.java +++ b/app/src/main/java/com/binbin/androidowner/Test.java @@ -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 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 7116a50..a3e80e0 100644 --- a/app/src/main/java/com/binbin/androidowner/User.kt +++ b/app/src/main/java/com/binbin/androidowner/User.kt @@ -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" + } +} diff --git a/app/src/main/java/com/binbin/androidowner/UserRestrict.kt b/app/src/main/java/com/binbin/androidowner/UserRestrict.kt index 82d78b1..73f9907 100644 --- a/app/src/main/java/com/binbin/androidowner/UserRestrict.kt +++ b/app/src/main/java/com/binbin/androidowner/UserRestrict.kt @@ -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)) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8f780a8..7274e80 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,11 +119,13 @@ 提供支持的短消息 如果你禁用了某个功能,用户尝试使用这个功能时会看见这个消息(可多行) 消息 - 预设名 + 用户名 提供支持的长消息 都是显示短消息,长消息不知道在哪里显示 禁止蓝牙分享通讯录 系统更新策略 管理系统更新策略 禁止锁屏(需无密码) + 用户管理 + 查看用户状态,添加用户 \ No newline at end of file