From d91d638b3a54764f5128df3c753db40bbab24b12 Mon Sep 17 00:00:00 2001 From: BinTianqi <1220958406@qq.com> Date: Tue, 6 Feb 2024 18:29:43 +0800 Subject: [PATCH] Ca cert manage, update readme.md --- Readme.md | 19 +++-- .../com/binbin/androidowner/DeviceControl.kt | 76 +++++++++++++++++-- .../com/binbin/androidowner/MainActivity.kt | 39 +++++++--- 3 files changed, 110 insertions(+), 24 deletions(-) diff --git a/Readme.md b/Readme.md index 21b5644..d9d690a 100644 --- a/Readme.md +++ b/Readme.md @@ -3,13 +3,20 @@ ### 简介 使用安卓的Device Admin和Device Owner,全方位掌控你的设备。 -这个应用使用Kotlin + Jetpack Compose,有不完全的质感设计3配色 -记得给我个小星星,欢迎大家提交Issue +### 优点 + +- 开源。Device owner权限仅次于Root权限,十分危险,闭源软件的安全性没有保证 +- UI友好。易于上手,使用Material design 3,支持系统主题色,适配手机和手表的屏幕尺寸 +- 与时俱进。使用 Kotlin + Jetpack Compose,支持安卓14的新功能 +- 维护中。这个项目正在不断更新。欢迎 Issue 和 Pull request + +### 缺点 + +不支持一些 Device admin 和 Device owner 功能。如果追求功能齐全,谷歌官方的 [TestDPC](https://github.com/googlesamples/android-testdpc) 可能更适合你 ### 功能 -已经适配手表,请在应用的设置里打开“Wear” 适配了一些预见式返回动画(需安卓13或14),可能不太稳定,如果有问题请向我反馈 - 设备控制 @@ -21,10 +28,11 @@ - 设置时间 - 管理系统更新策略 - 清除数据 -- 管理应用 +- 应用管理 - 隐藏应用 - 停用应用 - 禁止卸载应用 + - 应用权限管理 - 设置许可的输入法 - 用户限制 - 网络和互联网:禁止使用流量、WiFi、VPN、私人DNS @@ -53,7 +61,8 @@ - Managed Profile,工作资料和多用户相关 - ~~应用管理:安装/卸载应用~~(暂不考虑) - 应用管理:包选择器(目前只能手动输入包名) -- 用户管理:用户选择器(目前只能手动输入序列号) +- 应用管理:应用权限选择器 +- 用户管理:用户选择器(目前只能手动输入用户序列号) ### 许可证 diff --git a/app/src/main/java/com/binbin/androidowner/DeviceControl.kt b/app/src/main/java/com/binbin/androidowner/DeviceControl.kt index 72fc591..e99de45 100644 --- a/app/src/main/java/com/binbin/androidowner/DeviceControl.kt +++ b/app/src/main/java/com/binbin/androidowner/DeviceControl.kt @@ -4,7 +4,9 @@ import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager.* import android.content.ComponentName import android.content.Context +import android.content.Intent import android.os.Build.VERSION +import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity import androidx.compose.animation.AnimatedVisibility @@ -29,6 +31,8 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch @Composable fun DeviceControl(){ @@ -144,13 +148,6 @@ fun DeviceControl(){ } } } - if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ - Button( - onClick = {myDpm.uninstallAllUserCaCerts(myComponent);Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show()}, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) { - Text(text = "清除用户Ca证书") - }} if(VERSION.SDK_INT>=28){ Column(modifier = sections()){ @@ -368,6 +365,64 @@ fun DeviceControl(){ } } + if(isDeviceOwner(myDpm)||isProfileOwner(myDpm)){ + var exist by remember{mutableStateOf(false)} + var isEmpty by remember{mutableStateOf(false)} + val refresh = { + isEmpty = caCert.isEmpty() + exist = if(!isEmpty){ myDpm.hasCaCertInstalled(myComponent, caCert) }else{ false } + } + LaunchedEffect(exist){ launch{isCaCertSelected(600){refresh()}} } + Column(modifier = sections()){ + Text(text = "Ca证书", style = typography.titleLarge) + if(isEmpty){ Text(text = "请选择Ca证书(.0)") }else{ Text(text = "证书已安装:$exist") } + Button( + onClick = { + val caCertIntent = Intent(Intent.ACTION_GET_CONTENT) + caCertIntent.setType("*/*") + caCertIntent.addCategory(Intent.CATEGORY_OPENABLE) + getCaCert.launch(caCertIntent) + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("选择证书...") + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){ + Button( + onClick = { + val result = myDpm.installCaCert(myComponent, caCert) + Toast.makeText(myContext, if(result){"成功"}else{"失败"}, Toast.LENGTH_SHORT).show() + refresh() + }, + modifier = Modifier.fillMaxWidth(0.49F) + ) { + Text("安装") + } + Button( + onClick = { + if(exist){ + myDpm.uninstallCaCert(myComponent, caCert) + exist = myDpm.hasCaCertInstalled(myComponent, caCert) + Toast.makeText(myContext, if(exist){"失败"}else{"成功"}, Toast.LENGTH_SHORT).show() + }else{ Toast.makeText(myContext, "不存在", Toast.LENGTH_SHORT).show() } + }, + modifier = Modifier.fillMaxWidth(0.96F) + ) { + Text("卸载") + } + } + Button( + onClick = { + myDpm.uninstallAllUserCaCerts(myComponent) + Toast.makeText(myContext, "成功", Toast.LENGTH_SHORT).show() + }, + modifier = Modifier.fillMaxWidth() + ){ + Text("清除用户Ca证书") + } + } + } + if(isDeviceOwner(myDpm)){ SysUpdatePolicy() } @@ -470,3 +525,10 @@ fun DeviceCtrlItem( ) } } + +suspend fun isCaCertSelected(delay:Long,operation:()->Unit){ + while(true){ + delay(delay) + operation() + } +} diff --git a/app/src/main/java/com/binbin/androidowner/MainActivity.kt b/app/src/main/java/com/binbin/androidowner/MainActivity.kt index 75d1706..e758119 100644 --- a/app/src/main/java/com/binbin/androidowner/MainActivity.kt +++ b/app/src/main/java/com/binbin/androidowner/MainActivity.kt @@ -4,11 +4,14 @@ import android.annotation.SuppressLint import android.app.admin.DevicePolicyManager import android.content.ComponentName import android.content.Context +import android.content.Intent import android.os.Build.VERSION import android.os.Bundle +import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.animation.* +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -17,7 +20,8 @@ import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.Home import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme.typography -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -35,22 +39,32 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.binbin.androidowner.ui.theme.AndroidOwnerTheme +import java.io.FileNotFoundException +import java.io.IOException -/*lateinit var getOtaPackage: ActivityResultLauncher -var installOta = false -lateinit var otaUri:Uri*/ +lateinit var getCaCert: ActivityResultLauncher +var caCert = byteArrayOf() @ExperimentalMaterial3Api class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { WindowCompat.setDecorFitsSystemWindows(window, false) super.onCreate(savedInstanceState) - /*otaUri = Uri.EMPTY - getOtaPackage = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - val data = it.data?.data - installOta = true - otaUri = data ?: Uri.EMPTY - }*/ + getCaCert = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + val uri = it.data?.data + if(uri!=null){ + try{ + val stream = contentResolver.openInputStream(uri) + if(stream!=null) { + caCert = stream.readBytes() + if(caCert.size>50000){ Toast.makeText(applicationContext, "太大了", Toast.LENGTH_SHORT).show(); caCert = byteArrayOf() } + }else{ Toast.makeText(applicationContext, "空的流", Toast.LENGTH_SHORT).show() } + stream?.close() + } + catch(e:FileNotFoundException){ Toast.makeText(applicationContext, "文件不存在", Toast.LENGTH_SHORT).show() } + catch(e:IOException){ Toast.makeText(applicationContext, "IO异常", Toast.LENGTH_SHORT).show() } + }else{ Toast.makeText(applicationContext, "空URI", Toast.LENGTH_SHORT).show() } + } setContent { AndroidOwnerTheme { MyScaffold() @@ -148,6 +162,7 @@ fun HomePage(navCtrl:NavHostController){ val activateType = if(isDeviceOwner(myDpm)){"Device Owner"}else if(isProfileOwner(myDpm)){"Profile Owner"}else if(myDpm.isAdminActive(myComponent)){"Device Admin"}else{""} val sharedPref = LocalContext.current.getSharedPreferences("data", Context.MODE_PRIVATE) val isWear = sharedPref.getBoolean("isWear",false) + caCert = byteArrayOf() Column(modifier = Modifier.verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally) { if(isWear){ Spacer(Modifier.padding(vertical = 3.dp)) } Row( @@ -273,7 +288,7 @@ fun isProfileOwner(dpm:DevicePolicyManager): Boolean { return dpm.isProfileOwnerApp("com.binbin.androidowner") } -@SuppressLint("ModifierFactoryExtensionFunction") +@SuppressLint("ModifierFactoryExtensionFunction", "ComposableModifierFactory") @Composable fun sections(bgColor:Color=MaterialTheme.colorScheme.primaryContainer):Modifier{ val backgroundColor = if(isSystemInDarkTheme()){bgColor.copy(0.4F)}else{bgColor.copy(0.6F)}