diff --git a/Readme.md b/Readme.md
index e3d44e9..bcfd1ae 100644
--- a/Readme.md
+++ b/Readme.md
@@ -27,13 +27,14 @@
- 关闭USB信号(需设备支持)
- 设置时间
- 管理系统更新策略
- - 清除数据
+ - 恢复出厂设置
- 应用管理
- 隐藏应用
- 停用应用
- 禁止卸载应用
- 应用权限管理
- - 设置许可的输入法
+ - 清除应用数据
+ - 安装、卸载应用
- 用户限制
- 网络和互联网:禁止使用流量、WiFi、VPN、私人DNS
- 其他连接:禁止使用蓝牙、位置信息、NFC、USB(MTP)
@@ -50,15 +51,15 @@
- 最大密码错误次数
- 密码失效超时时间
- 设置密码复杂度要求
+ - 修改锁屏可用功能
### 这个应用十分危险!!!
在使用各个功能之前,请仔细阅读相应的说明。红色的按钮一定要谨慎使用!
如果操作不慎,可能会意外地丢失数据或者让你无法解锁你的设备!
-### 即将加入的功能
+### 正在开发的功能
-- ~~应用管理:安装/卸载应用~~(暂不考虑)
- 应用管理:包选择器(目前只能手动输入包名)
- 应用管理:应用权限选择器
- 用户管理:用户选择器(目前只能手动输入用户序列号)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 933dcf7..991dbe7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,6 +16,8 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt b/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt
index c910033..f098f67 100644
--- a/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt
+++ b/app/src/main/java/com/binbin/androidowner/ApplicationManage.kt
@@ -1,5 +1,6 @@
package com.binbin.androidowner
+import android.app.PendingIntent
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
@@ -11,6 +12,7 @@ import android.app.admin.PackagePolicy.PACKAGE_POLICY_BLOCKLIST
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageInstaller
import android.content.pm.PackageManager.NameNotFoundException
import android.net.Uri
import android.os.Build.VERSION
@@ -39,6 +41,8 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
+import java.io.IOException
+import java.io.InputStream
import java.util.concurrent.Executors
private var credentialList = mutableSetOf()
@@ -527,6 +531,71 @@ fun ApplicationManage(){
Text("设为默认拨号应用")
}
}
+
+ Column(modifier = sections()){
+ Text(text = "卸载应用", style = typography.titleLarge)
+ Text(text = "静默卸载需Device owner", style = bodyTextStyle)
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){
+ Button(
+ onClick = {
+ val intent = Intent(myContext,PackageInstallerReceiver::class.java)
+ val intentSender = PendingIntent.getBroadcast(myContext, 8, intent, PendingIntent.FLAG_IMMUTABLE).intentSender
+ val pkgInstaller = myContext.packageManager.packageInstaller
+ pkgInstaller.uninstall(pkgName, intentSender)
+ },
+ modifier = Modifier.fillMaxWidth(0.49F)
+ ) {
+ Text("静默卸载")
+ }
+ Button(
+ onClick = {
+ val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE)
+ intent.setData(Uri.parse("package:$pkgName"))
+ myContext.startActivity(intent)
+ },
+ modifier = Modifier.fillMaxWidth(0.96F)
+ ) {
+ Text("请求卸载")
+ }
+ }
+ }
+
+ Column(modifier = sections()){
+ Text(text = "安装应用", style = typography.titleLarge)
+ Text(text = "静默安装需Device owner", style = bodyTextStyle)
+ Button(
+ onClick = {
+ focusMgr.clearFocus()
+ val installApkIntent = Intent(Intent.ACTION_GET_CONTENT)
+ installApkIntent.setType("application/vnd.android.package-archive")
+ installApkIntent.addCategory(Intent.CATEGORY_OPENABLE)
+ getApk.launch(installApkIntent)
+ },
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text("选择APK...")
+ }
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween){
+ Button(
+ onClick = { uriToStream(myContext, apkUri){stream -> installPackage(myContext,stream)} },
+ modifier = Modifier.fillMaxWidth(0.49F)
+ ) {
+ Text("静默安装")
+ }
+ Button(
+ onClick = {
+ val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
+ intent.setData(apkUri)
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ myContext.startActivity(intent)
+ },
+ modifier = Modifier.fillMaxWidth(0.96F)
+ ) {
+ Text("请求安装")
+ }
+ }
+ }
+
Spacer(Modifier.padding(30.dp))
}
}
@@ -557,3 +626,22 @@ private fun AppManageItem(
)
}
}
+
+@Throws(IOException::class)
+private fun installPackage(context: Context, inputStream: InputStream){
+ val packageInstaller = context.packageManager.packageInstaller
+ val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
+ 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()
+ val pendingIntent = PendingIntent.getBroadcast(context, sessionId, Intent(context,PackageInstallerReceiver::class.java), PendingIntent.FLAG_IMMUTABLE).intentSender
+ session.commit(pendingIntent)
+}
diff --git a/app/src/main/java/com/binbin/androidowner/MainActivity.kt b/app/src/main/java/com/binbin/androidowner/MainActivity.kt
index 1bd8c7c..0630bea 100644
--- a/app/src/main/java/com/binbin/androidowner/MainActivity.kt
+++ b/app/src/main/java/com/binbin/androidowner/MainActivity.kt
@@ -6,6 +6,7 @@ import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.net.Uri
import android.os.Build.VERSION
import android.os.Bundle
import android.os.UserManager
@@ -42,10 +43,14 @@ import androidx.navigation.compose.rememberNavController
import com.binbin.androidowner.ui.theme.AndroidOwnerTheme
import java.io.FileNotFoundException
import java.io.IOException
+import java.io.InputStream
+
lateinit var getCaCert: ActivityResultLauncher
lateinit var createUser:ActivityResultLauncher
lateinit var createManagedProfile:ActivityResultLauncher
+lateinit var getApk:ActivityResultLauncher
+lateinit var apkUri: Uri
var caCert = byteArrayOf()
@ExperimentalMaterial3Api
@@ -53,20 +58,16 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
- getCaCert = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ getApk = 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() }
+ if(uri!=null){ apkUri = uri }
+ else{ Toast.makeText(applicationContext, "空URI", Toast.LENGTH_SHORT).show() }
+ }
+ getCaCert = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ uriToStream(applicationContext,it.data?.data){stream->
+ caCert = stream.readBytes()
+ if(caCert.size>50000){ Toast.makeText(applicationContext, "太大了", Toast.LENGTH_SHORT).show(); caCert = byteArrayOf() }
+ }
}
createUser = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
when(it.resultCode){
@@ -340,3 +341,21 @@ fun sections(bgColor:Color=MaterialTheme.colorScheme.primaryContainer):Modifier{
.padding(vertical = 2.dp, horizontal = 3.dp)
}
}
+
+fun uriToStream(
+ context: Context,
+ uri: Uri?,
+ operation:(stream:InputStream)->Unit
+){
+ if(uri!=null){
+ apkUri = uri
+ try{
+ val stream = context.contentResolver.openInputStream(uri)
+ if(stream!=null) { operation(stream) }
+ else{ Toast.makeText(context, "空的流", Toast.LENGTH_SHORT).show() }
+ stream?.close()
+ }
+ catch(e:FileNotFoundException){ Toast.makeText(context, "文件不存在", Toast.LENGTH_SHORT).show() }
+ catch(e:IOException){ Toast.makeText(context, "IO异常", Toast.LENGTH_SHORT).show() }
+ }else{ Toast.makeText(context, "空URI", Toast.LENGTH_SHORT).show() }
+}
diff --git a/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt b/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt
index 535bcff..abb2a10 100644
--- a/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt
+++ b/app/src/main/java/com/binbin/androidowner/ManagedProfile.kt
@@ -65,7 +65,7 @@ fun ManagedProfile() {
Text("跳转至个人应用")
}
}else{
- if(!myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)){
+ if(!myDpm.isProvisioningAllowed(ACTION_PROVISION_MANAGED_PROFILE)&&!isDeviceOwner(myDpm)){
Button(
onClick = { myContext.startActivity(Intent("com.binbin.androidowner.MAIN_ACTION")) }, modifier = Modifier.fillMaxWidth()
){
diff --git a/app/src/main/java/com/binbin/androidowner/Permissions.kt b/app/src/main/java/com/binbin/androidowner/Permissions.kt
index dcb37e8..877a580 100644
--- a/app/src/main/java/com/binbin/androidowner/Permissions.kt
+++ b/app/src/main/java/com/binbin/androidowner/Permissions.kt
@@ -231,11 +231,7 @@ fun DpmPermissions(navCtrl:NavHostController){
if((VERSION.SDK_INT>=26&&isDeviceOwner(myDpm))||(VERSION.SDK_INT>=24&&isProfileOwner(myDpm))){
Column(modifier = sections()){
- var orgName by remember{
- mutableStateOf(
- if(myDpm.getOrganizationName(myComponent).toString()=="null"){ "" }else{ myDpm.getOrganizationName(myComponent).toString() }
- )
- }
+ var orgName by remember{mutableStateOf(try{myDpm.getOrganizationName(myComponent).toString()}catch(e:SecurityException){""})}
Text(text = "组织名称", style = typography.titleLarge)
TextField(
value = orgName, onValueChange = {orgName=it}, modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp),
diff --git a/app/src/main/java/com/binbin/androidowner/Receiver.kt b/app/src/main/java/com/binbin/androidowner/Receiver.kt
index ecaeac8..ba7aa5e 100644
--- a/app/src/main/java/com/binbin/androidowner/Receiver.kt
+++ b/app/src/main/java/com/binbin/androidowner/Receiver.kt
@@ -2,10 +2,12 @@ package com.binbin.androidowner
import android.annotation.SuppressLint
import android.app.admin.DeviceAdminReceiver
+import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import android.os.Build
+import android.content.pm.PackageInstaller.*
import android.os.Build.VERSION
+import android.util.Log
import android.widget.Toast
class MyDeviceAdminReceiver : DeviceAdminReceiver() {
@@ -33,4 +35,23 @@ class MyDeviceAdminReceiver : DeviceAdminReceiver() {
if (VERSION.SDK_INT < 26) { return }
Toast.makeText(context, "新的系统更新!", Toast.LENGTH_SHORT).show()
}
-}
\ No newline at end of file
+}
+
+class PackageInstallerReceiver:BroadcastReceiver(){
+ override fun onReceive(context: Context, intent: Intent) {
+ val toastText = when(intent.getIntExtra(EXTRA_STATUS,666)){
+ STATUS_PENDING_USER_ACTION->"等待用户交互"
+ STATUS_SUCCESS->"成功"
+ STATUS_FAILURE->"失败"
+ STATUS_FAILURE_BLOCKED->"失败:被阻止"
+ STATUS_FAILURE_ABORTED->"失败:被打断"
+ STATUS_FAILURE_INVALID->"失败:无效"
+ STATUS_FAILURE_CONFLICT->"失败:冲突"
+ STATUS_FAILURE_STORAGE->"失败:空间不足"
+ STATUS_FAILURE_INCOMPATIBLE->"失败:不兼容"
+ STATUS_FAILURE_TIMEOUT->"失败:超时"
+ else->"未知"
+ }
+ Log.e("静默安装","${intent.getIntExtra(EXTRA_STATUS,666)}:$toastText")
+ }
+}